...

Source file src/go.etcd.io/etcd/client/v2/keys_test.go

Documentation: go.etcd.io/etcd/client/v2

     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 client
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"io/ioutil"
    22  	"net/http"
    23  	"net/url"
    24  	"reflect"
    25  	"testing"
    26  	"time"
    27  )
    28  
    29  func TestV2KeysURLHelper(t *testing.T) {
    30  	tests := []struct {
    31  		endpoint url.URL
    32  		prefix   string
    33  		key      string
    34  		want     url.URL
    35  	}{
    36  		// key is empty, no problem
    37  		{
    38  			endpoint: url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys"},
    39  			prefix:   "",
    40  			key:      "",
    41  			want:     url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys"},
    42  		},
    43  
    44  		// key is joined to path
    45  		{
    46  			endpoint: url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys"},
    47  			prefix:   "",
    48  			key:      "/foo/bar",
    49  			want:     url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys/foo/bar"},
    50  		},
    51  
    52  		// key is joined to path when path is empty
    53  		{
    54  			endpoint: url.URL{Scheme: "http", Host: "example.com", Path: ""},
    55  			prefix:   "",
    56  			key:      "/foo/bar",
    57  			want:     url.URL{Scheme: "http", Host: "example.com", Path: "/foo/bar"},
    58  		},
    59  
    60  		// Host field carries through with port
    61  		{
    62  			endpoint: url.URL{Scheme: "http", Host: "example.com:8080", Path: "/v2/keys"},
    63  			prefix:   "",
    64  			key:      "",
    65  			want:     url.URL{Scheme: "http", Host: "example.com:8080", Path: "/v2/keys"},
    66  		},
    67  
    68  		// Scheme carries through
    69  		{
    70  			endpoint: url.URL{Scheme: "https", Host: "example.com", Path: "/v2/keys"},
    71  			prefix:   "",
    72  			key:      "",
    73  			want:     url.URL{Scheme: "https", Host: "example.com", Path: "/v2/keys"},
    74  		},
    75  		// Prefix is applied
    76  		{
    77  			endpoint: url.URL{Scheme: "https", Host: "example.com", Path: "/foo"},
    78  			prefix:   "/bar",
    79  			key:      "/baz",
    80  			want:     url.URL{Scheme: "https", Host: "example.com", Path: "/foo/bar/baz"},
    81  		},
    82  		// Prefix is joined to path
    83  		{
    84  			endpoint: url.URL{Scheme: "https", Host: "example.com", Path: "/foo"},
    85  			prefix:   "/bar",
    86  			key:      "",
    87  			want:     url.URL{Scheme: "https", Host: "example.com", Path: "/foo/bar"},
    88  		},
    89  		// Keep trailing slash
    90  		{
    91  			endpoint: url.URL{Scheme: "https", Host: "example.com", Path: "/foo"},
    92  			prefix:   "/bar",
    93  			key:      "/baz/",
    94  			want:     url.URL{Scheme: "https", Host: "example.com", Path: "/foo/bar/baz/"},
    95  		},
    96  	}
    97  
    98  	for i, tt := range tests {
    99  		got := v2KeysURL(tt.endpoint, tt.prefix, tt.key)
   100  		if tt.want != *got {
   101  			t.Errorf("#%d: want=%#v, got=%#v", i, tt.want, *got)
   102  		}
   103  	}
   104  }
   105  
   106  func TestGetAction(t *testing.T) {
   107  	ep := url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys"}
   108  	baseWantURL := &url.URL{
   109  		Scheme: "http",
   110  		Host:   "example.com",
   111  		Path:   "/v2/keys/foo/bar",
   112  	}
   113  	wantHeader := http.Header{}
   114  
   115  	tests := []struct {
   116  		recursive bool
   117  		sorted    bool
   118  		quorum    bool
   119  		wantQuery string
   120  	}{
   121  		{
   122  			recursive: false,
   123  			sorted:    false,
   124  			quorum:    false,
   125  			wantQuery: "quorum=false&recursive=false&sorted=false",
   126  		},
   127  		{
   128  			recursive: true,
   129  			sorted:    false,
   130  			quorum:    false,
   131  			wantQuery: "quorum=false&recursive=true&sorted=false",
   132  		},
   133  		{
   134  			recursive: false,
   135  			sorted:    true,
   136  			quorum:    false,
   137  			wantQuery: "quorum=false&recursive=false&sorted=true",
   138  		},
   139  		{
   140  			recursive: true,
   141  			sorted:    true,
   142  			quorum:    false,
   143  			wantQuery: "quorum=false&recursive=true&sorted=true",
   144  		},
   145  		{
   146  			recursive: false,
   147  			sorted:    false,
   148  			quorum:    true,
   149  			wantQuery: "quorum=true&recursive=false&sorted=false",
   150  		},
   151  	}
   152  
   153  	for i, tt := range tests {
   154  		f := getAction{
   155  			Key:       "/foo/bar",
   156  			Recursive: tt.recursive,
   157  			Sorted:    tt.sorted,
   158  			Quorum:    tt.quorum,
   159  		}
   160  		got := *f.HTTPRequest(ep)
   161  
   162  		wantURL := baseWantURL
   163  		wantURL.RawQuery = tt.wantQuery
   164  
   165  		err := assertRequest(got, "GET", wantURL, wantHeader, nil)
   166  		if err != nil {
   167  			t.Errorf("#%d: %v", i, err)
   168  		}
   169  	}
   170  }
   171  
   172  func TestWaitAction(t *testing.T) {
   173  	ep := url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys"}
   174  	baseWantURL := &url.URL{
   175  		Scheme: "http",
   176  		Host:   "example.com",
   177  		Path:   "/v2/keys/foo/bar",
   178  	}
   179  	wantHeader := http.Header{}
   180  
   181  	tests := []struct {
   182  		waitIndex uint64
   183  		recursive bool
   184  		wantQuery string
   185  	}{
   186  		{
   187  			recursive: false,
   188  			waitIndex: uint64(0),
   189  			wantQuery: "recursive=false&wait=true&waitIndex=0",
   190  		},
   191  		{
   192  			recursive: false,
   193  			waitIndex: uint64(12),
   194  			wantQuery: "recursive=false&wait=true&waitIndex=12",
   195  		},
   196  		{
   197  			recursive: true,
   198  			waitIndex: uint64(12),
   199  			wantQuery: "recursive=true&wait=true&waitIndex=12",
   200  		},
   201  	}
   202  
   203  	for i, tt := range tests {
   204  		f := waitAction{
   205  			Key:       "/foo/bar",
   206  			WaitIndex: tt.waitIndex,
   207  			Recursive: tt.recursive,
   208  		}
   209  		got := *f.HTTPRequest(ep)
   210  
   211  		wantURL := baseWantURL
   212  		wantURL.RawQuery = tt.wantQuery
   213  
   214  		err := assertRequest(got, "GET", wantURL, wantHeader, nil)
   215  		if err != nil {
   216  			t.Errorf("#%d: unexpected error: %#v", i, err)
   217  		}
   218  	}
   219  }
   220  
   221  func TestSetAction(t *testing.T) {
   222  	wantHeader := http.Header(map[string][]string{
   223  		"Content-Type": {"application/x-www-form-urlencoded"},
   224  	})
   225  
   226  	tests := []struct {
   227  		act      setAction
   228  		wantURL  string
   229  		wantBody string
   230  	}{
   231  		// default prefix
   232  		{
   233  			act: setAction{
   234  				Prefix: defaultV2KeysPrefix,
   235  				Key:    "foo",
   236  			},
   237  			wantURL:  "http://example.com/v2/keys/foo",
   238  			wantBody: "value=",
   239  		},
   240  
   241  		// non-default prefix
   242  		{
   243  			act: setAction{
   244  				Prefix: "/pfx",
   245  				Key:    "foo",
   246  			},
   247  			wantURL:  "http://example.com/pfx/foo",
   248  			wantBody: "value=",
   249  		},
   250  
   251  		// no prefix
   252  		{
   253  			act: setAction{
   254  				Key: "foo",
   255  			},
   256  			wantURL:  "http://example.com/foo",
   257  			wantBody: "value=",
   258  		},
   259  
   260  		// Key with path separators
   261  		{
   262  			act: setAction{
   263  				Prefix: defaultV2KeysPrefix,
   264  				Key:    "foo/bar/baz",
   265  			},
   266  			wantURL:  "http://example.com/v2/keys/foo/bar/baz",
   267  			wantBody: "value=",
   268  		},
   269  
   270  		// Key with leading slash, Prefix with trailing slash
   271  		{
   272  			act: setAction{
   273  				Prefix: "/foo/",
   274  				Key:    "/bar",
   275  			},
   276  			wantURL:  "http://example.com/foo/bar",
   277  			wantBody: "value=",
   278  		},
   279  
   280  		// Key with trailing slash
   281  		{
   282  			act: setAction{
   283  				Key: "/foo/",
   284  			},
   285  			wantURL:  "http://example.com/foo/",
   286  			wantBody: "value=",
   287  		},
   288  
   289  		// Value is set
   290  		{
   291  			act: setAction{
   292  				Key:   "foo",
   293  				Value: "baz",
   294  			},
   295  			wantURL:  "http://example.com/foo",
   296  			wantBody: "value=baz",
   297  		},
   298  
   299  		// PrevExist set, but still ignored
   300  		{
   301  			act: setAction{
   302  				Key:       "foo",
   303  				PrevExist: PrevIgnore,
   304  			},
   305  			wantURL:  "http://example.com/foo",
   306  			wantBody: "value=",
   307  		},
   308  
   309  		// PrevExist set to true
   310  		{
   311  			act: setAction{
   312  				Key:       "foo",
   313  				PrevExist: PrevExist,
   314  			},
   315  			wantURL:  "http://example.com/foo?prevExist=true",
   316  			wantBody: "value=",
   317  		},
   318  
   319  		// PrevExist set to false
   320  		{
   321  			act: setAction{
   322  				Key:       "foo",
   323  				PrevExist: PrevNoExist,
   324  			},
   325  			wantURL:  "http://example.com/foo?prevExist=false",
   326  			wantBody: "value=",
   327  		},
   328  
   329  		// PrevValue is urlencoded
   330  		{
   331  			act: setAction{
   332  				Key:       "foo",
   333  				PrevValue: "bar baz",
   334  			},
   335  			wantURL:  "http://example.com/foo?prevValue=bar+baz",
   336  			wantBody: "value=",
   337  		},
   338  
   339  		// PrevIndex is set
   340  		{
   341  			act: setAction{
   342  				Key:       "foo",
   343  				PrevIndex: uint64(12),
   344  			},
   345  			wantURL:  "http://example.com/foo?prevIndex=12",
   346  			wantBody: "value=",
   347  		},
   348  
   349  		// TTL is set
   350  		{
   351  			act: setAction{
   352  				Key: "foo",
   353  				TTL: 3 * time.Minute,
   354  			},
   355  			wantURL:  "http://example.com/foo",
   356  			wantBody: "ttl=180&value=",
   357  		},
   358  
   359  		// Refresh is set
   360  		{
   361  			act: setAction{
   362  				Key:     "foo",
   363  				TTL:     3 * time.Minute,
   364  				Refresh: true,
   365  			},
   366  			wantURL:  "http://example.com/foo",
   367  			wantBody: "refresh=true&ttl=180&value=",
   368  		},
   369  
   370  		// Dir is set
   371  		{
   372  			act: setAction{
   373  				Key: "foo",
   374  				Dir: true,
   375  			},
   376  			wantURL:  "http://example.com/foo?dir=true",
   377  			wantBody: "",
   378  		},
   379  		// Dir is set with a value
   380  		{
   381  			act: setAction{
   382  				Key:   "foo",
   383  				Value: "bar",
   384  				Dir:   true,
   385  			},
   386  			wantURL:  "http://example.com/foo?dir=true",
   387  			wantBody: "",
   388  		},
   389  		// Dir is set with PrevExist set to true
   390  		{
   391  			act: setAction{
   392  				Key:       "foo",
   393  				PrevExist: PrevExist,
   394  				Dir:       true,
   395  			},
   396  			wantURL:  "http://example.com/foo?dir=true&prevExist=true",
   397  			wantBody: "",
   398  		},
   399  		// Dir is set with PrevValue
   400  		{
   401  			act: setAction{
   402  				Key:       "foo",
   403  				PrevValue: "bar",
   404  				Dir:       true,
   405  			},
   406  			wantURL:  "http://example.com/foo?dir=true",
   407  			wantBody: "",
   408  		},
   409  		// NoValueOnSuccess is set
   410  		{
   411  			act: setAction{
   412  				Key:              "foo",
   413  				NoValueOnSuccess: true,
   414  			},
   415  			wantURL:  "http://example.com/foo?noValueOnSuccess=true",
   416  			wantBody: "value=",
   417  		},
   418  	}
   419  
   420  	for i, tt := range tests {
   421  		u, err := url.Parse(tt.wantURL)
   422  		if err != nil {
   423  			t.Errorf("#%d: unable to use wantURL fixture: %v", i, err)
   424  		}
   425  
   426  		got := tt.act.HTTPRequest(url.URL{Scheme: "http", Host: "example.com"})
   427  		if err := assertRequest(*got, "PUT", u, wantHeader, []byte(tt.wantBody)); err != nil {
   428  			t.Errorf("#%d: %v", i, err)
   429  		}
   430  	}
   431  }
   432  
   433  func TestCreateInOrderAction(t *testing.T) {
   434  	wantHeader := http.Header(map[string][]string{
   435  		"Content-Type": {"application/x-www-form-urlencoded"},
   436  	})
   437  
   438  	tests := []struct {
   439  		act      createInOrderAction
   440  		wantURL  string
   441  		wantBody string
   442  	}{
   443  		// default prefix
   444  		{
   445  			act: createInOrderAction{
   446  				Prefix: defaultV2KeysPrefix,
   447  				Dir:    "foo",
   448  			},
   449  			wantURL:  "http://example.com/v2/keys/foo",
   450  			wantBody: "value=",
   451  		},
   452  
   453  		// non-default prefix
   454  		{
   455  			act: createInOrderAction{
   456  				Prefix: "/pfx",
   457  				Dir:    "foo",
   458  			},
   459  			wantURL:  "http://example.com/pfx/foo",
   460  			wantBody: "value=",
   461  		},
   462  
   463  		// no prefix
   464  		{
   465  			act: createInOrderAction{
   466  				Dir: "foo",
   467  			},
   468  			wantURL:  "http://example.com/foo",
   469  			wantBody: "value=",
   470  		},
   471  
   472  		// Key with path separators
   473  		{
   474  			act: createInOrderAction{
   475  				Prefix: defaultV2KeysPrefix,
   476  				Dir:    "foo/bar/baz",
   477  			},
   478  			wantURL:  "http://example.com/v2/keys/foo/bar/baz",
   479  			wantBody: "value=",
   480  		},
   481  
   482  		// Key with leading slash, Prefix with trailing slash
   483  		{
   484  			act: createInOrderAction{
   485  				Prefix: "/foo/",
   486  				Dir:    "/bar",
   487  			},
   488  			wantURL:  "http://example.com/foo/bar",
   489  			wantBody: "value=",
   490  		},
   491  
   492  		// Key with trailing slash
   493  		{
   494  			act: createInOrderAction{
   495  				Dir: "/foo/",
   496  			},
   497  			wantURL:  "http://example.com/foo/",
   498  			wantBody: "value=",
   499  		},
   500  
   501  		// Value is set
   502  		{
   503  			act: createInOrderAction{
   504  				Dir:   "foo",
   505  				Value: "baz",
   506  			},
   507  			wantURL:  "http://example.com/foo",
   508  			wantBody: "value=baz",
   509  		},
   510  		// TTL is set
   511  		{
   512  			act: createInOrderAction{
   513  				Dir: "foo",
   514  				TTL: 3 * time.Minute,
   515  			},
   516  			wantURL:  "http://example.com/foo",
   517  			wantBody: "ttl=180&value=",
   518  		},
   519  	}
   520  
   521  	for i, tt := range tests {
   522  		u, err := url.Parse(tt.wantURL)
   523  		if err != nil {
   524  			t.Errorf("#%d: unable to use wantURL fixture: %v", i, err)
   525  		}
   526  
   527  		got := tt.act.HTTPRequest(url.URL{Scheme: "http", Host: "example.com"})
   528  		if err := assertRequest(*got, "POST", u, wantHeader, []byte(tt.wantBody)); err != nil {
   529  			t.Errorf("#%d: %v", i, err)
   530  		}
   531  	}
   532  }
   533  
   534  func TestDeleteAction(t *testing.T) {
   535  	wantHeader := http.Header(map[string][]string{
   536  		"Content-Type": {"application/x-www-form-urlencoded"},
   537  	})
   538  
   539  	tests := []struct {
   540  		act     deleteAction
   541  		wantURL string
   542  	}{
   543  		// default prefix
   544  		{
   545  			act: deleteAction{
   546  				Prefix: defaultV2KeysPrefix,
   547  				Key:    "foo",
   548  			},
   549  			wantURL: "http://example.com/v2/keys/foo",
   550  		},
   551  
   552  		// non-default prefix
   553  		{
   554  			act: deleteAction{
   555  				Prefix: "/pfx",
   556  				Key:    "foo",
   557  			},
   558  			wantURL: "http://example.com/pfx/foo",
   559  		},
   560  
   561  		// no prefix
   562  		{
   563  			act: deleteAction{
   564  				Key: "foo",
   565  			},
   566  			wantURL: "http://example.com/foo",
   567  		},
   568  
   569  		// Key with path separators
   570  		{
   571  			act: deleteAction{
   572  				Prefix: defaultV2KeysPrefix,
   573  				Key:    "foo/bar/baz",
   574  			},
   575  			wantURL: "http://example.com/v2/keys/foo/bar/baz",
   576  		},
   577  
   578  		// Key with leading slash, Prefix with trailing slash
   579  		{
   580  			act: deleteAction{
   581  				Prefix: "/foo/",
   582  				Key:    "/bar",
   583  			},
   584  			wantURL: "http://example.com/foo/bar",
   585  		},
   586  
   587  		// Key with trailing slash
   588  		{
   589  			act: deleteAction{
   590  				Key: "/foo/",
   591  			},
   592  			wantURL: "http://example.com/foo/",
   593  		},
   594  
   595  		// Recursive set to true
   596  		{
   597  			act: deleteAction{
   598  				Key:       "foo",
   599  				Recursive: true,
   600  			},
   601  			wantURL: "http://example.com/foo?recursive=true",
   602  		},
   603  
   604  		// PrevValue is urlencoded
   605  		{
   606  			act: deleteAction{
   607  				Key:       "foo",
   608  				PrevValue: "bar baz",
   609  			},
   610  			wantURL: "http://example.com/foo?prevValue=bar+baz",
   611  		},
   612  
   613  		// PrevIndex is set
   614  		{
   615  			act: deleteAction{
   616  				Key:       "foo",
   617  				PrevIndex: uint64(12),
   618  			},
   619  			wantURL: "http://example.com/foo?prevIndex=12",
   620  		},
   621  	}
   622  
   623  	for i, tt := range tests {
   624  		u, err := url.Parse(tt.wantURL)
   625  		if err != nil {
   626  			t.Errorf("#%d: unable to use wantURL fixture: %v", i, err)
   627  		}
   628  
   629  		got := tt.act.HTTPRequest(url.URL{Scheme: "http", Host: "example.com"})
   630  		if err := assertRequest(*got, "DELETE", u, wantHeader, nil); err != nil {
   631  			t.Errorf("#%d: %v", i, err)
   632  		}
   633  	}
   634  }
   635  
   636  func assertRequest(got http.Request, wantMethod string, wantURL *url.URL, wantHeader http.Header, wantBody []byte) error {
   637  	if wantMethod != got.Method {
   638  		return fmt.Errorf("want.Method=%#v got.Method=%#v", wantMethod, got.Method)
   639  	}
   640  
   641  	if !reflect.DeepEqual(wantURL, got.URL) {
   642  		return fmt.Errorf("want.URL=%#v got.URL=%#v", wantURL, got.URL)
   643  	}
   644  
   645  	if !reflect.DeepEqual(wantHeader, got.Header) {
   646  		return fmt.Errorf("want.Header=%#v got.Header=%#v", wantHeader, got.Header)
   647  	}
   648  
   649  	if got.Body == nil {
   650  		if wantBody != nil {
   651  			return fmt.Errorf("want.Body=%v got.Body=%v", wantBody, got.Body)
   652  		}
   653  	} else {
   654  		if wantBody == nil {
   655  			return fmt.Errorf("want.Body=%v got.Body=%s", wantBody, got.Body)
   656  		}
   657  		gotBytes, err := ioutil.ReadAll(got.Body)
   658  		if err != nil {
   659  			return err
   660  		}
   661  
   662  		if !reflect.DeepEqual(wantBody, gotBytes) {
   663  			return fmt.Errorf("want.Body=%s got.Body=%s", wantBody, gotBytes)
   664  		}
   665  	}
   666  
   667  	return nil
   668  }
   669  
   670  func TestUnmarshalSuccessfulResponse(t *testing.T) {
   671  	var expiration time.Time
   672  	expiration.UnmarshalText([]byte("2015-04-07T04:40:23.044979686Z"))
   673  
   674  	tests := []struct {
   675  		indexHdr     string
   676  		clusterIDHdr string
   677  		body         string
   678  		wantRes      *Response
   679  		wantErr      bool
   680  	}{
   681  		// Neither PrevNode or Node
   682  		{
   683  			indexHdr: "1",
   684  			body:     `{"action":"delete"}`,
   685  			wantRes:  &Response{Action: "delete", Index: 1},
   686  			wantErr:  false,
   687  		},
   688  
   689  		// PrevNode
   690  		{
   691  			indexHdr: "15",
   692  			body:     `{"action":"delete", "prevNode": {"key": "/foo", "value": "bar", "modifiedIndex": 12, "createdIndex": 10}}`,
   693  			wantRes: &Response{
   694  				Action: "delete",
   695  				Index:  15,
   696  				Node:   nil,
   697  				PrevNode: &Node{
   698  					Key:           "/foo",
   699  					Value:         "bar",
   700  					ModifiedIndex: 12,
   701  					CreatedIndex:  10,
   702  				},
   703  			},
   704  			wantErr: false,
   705  		},
   706  
   707  		// Node
   708  		{
   709  			indexHdr: "15",
   710  			body:     `{"action":"get", "node": {"key": "/foo", "value": "bar", "modifiedIndex": 12, "createdIndex": 10, "ttl": 10, "expiration": "2015-04-07T04:40:23.044979686Z"}}`,
   711  			wantRes: &Response{
   712  				Action: "get",
   713  				Index:  15,
   714  				Node: &Node{
   715  					Key:           "/foo",
   716  					Value:         "bar",
   717  					ModifiedIndex: 12,
   718  					CreatedIndex:  10,
   719  					TTL:           10,
   720  					Expiration:    &expiration,
   721  				},
   722  				PrevNode: nil,
   723  			},
   724  			wantErr: false,
   725  		},
   726  
   727  		// Node Dir
   728  		{
   729  			indexHdr:     "15",
   730  			clusterIDHdr: "abcdef",
   731  			body:         `{"action":"get", "node": {"key": "/foo", "dir": true, "modifiedIndex": 12, "createdIndex": 10}}`,
   732  			wantRes: &Response{
   733  				Action: "get",
   734  				Index:  15,
   735  				Node: &Node{
   736  					Key:           "/foo",
   737  					Dir:           true,
   738  					ModifiedIndex: 12,
   739  					CreatedIndex:  10,
   740  				},
   741  				PrevNode:  nil,
   742  				ClusterID: "abcdef",
   743  			},
   744  			wantErr: false,
   745  		},
   746  
   747  		// PrevNode and Node
   748  		{
   749  			indexHdr: "15",
   750  			body:     `{"action":"update", "prevNode": {"key": "/foo", "value": "baz", "modifiedIndex": 10, "createdIndex": 10}, "node": {"key": "/foo", "value": "bar", "modifiedIndex": 12, "createdIndex": 10}}`,
   751  			wantRes: &Response{
   752  				Action: "update",
   753  				Index:  15,
   754  				PrevNode: &Node{
   755  					Key:           "/foo",
   756  					Value:         "baz",
   757  					ModifiedIndex: 10,
   758  					CreatedIndex:  10,
   759  				},
   760  				Node: &Node{
   761  					Key:           "/foo",
   762  					Value:         "bar",
   763  					ModifiedIndex: 12,
   764  					CreatedIndex:  10,
   765  				},
   766  			},
   767  			wantErr: false,
   768  		},
   769  
   770  		// Garbage in body
   771  		{
   772  			indexHdr: "",
   773  			body:     `garbage`,
   774  			wantRes:  nil,
   775  			wantErr:  true,
   776  		},
   777  
   778  		// non-integer index
   779  		{
   780  			indexHdr: "poo",
   781  			body:     `{}`,
   782  			wantRes:  nil,
   783  			wantErr:  true,
   784  		},
   785  	}
   786  
   787  	for i, tt := range tests {
   788  		h := make(http.Header)
   789  		h.Add("X-Etcd-Index", tt.indexHdr)
   790  		res, err := unmarshalSuccessfulKeysResponse(h, []byte(tt.body))
   791  		if tt.wantErr != (err != nil) {
   792  			t.Errorf("#%d: wantErr=%t, err=%v", i, tt.wantErr, err)
   793  		}
   794  
   795  		if (res == nil) != (tt.wantRes == nil) {
   796  			t.Errorf("#%d: received res=%#v, but expected res=%#v", i, res, tt.wantRes)
   797  			continue
   798  		} else if tt.wantRes == nil {
   799  			// expected and successfully got nil response
   800  			continue
   801  		}
   802  
   803  		if res.Action != tt.wantRes.Action {
   804  			t.Errorf("#%d: Action=%s, expected %s", i, res.Action, tt.wantRes.Action)
   805  		}
   806  		if res.Index != tt.wantRes.Index {
   807  			t.Errorf("#%d: Index=%d, expected %d", i, res.Index, tt.wantRes.Index)
   808  		}
   809  		if !reflect.DeepEqual(res.Node, tt.wantRes.Node) {
   810  			t.Errorf("#%d: Node=%v, expected %v", i, res.Node, tt.wantRes.Node)
   811  		}
   812  	}
   813  }
   814  
   815  func TestUnmarshalFailedKeysResponse(t *testing.T) {
   816  	body := []byte(`{"errorCode":100,"message":"Key not found","cause":"/foo","index":18}`)
   817  
   818  	wantErr := Error{
   819  		Code:    100,
   820  		Message: "Key not found",
   821  		Cause:   "/foo",
   822  		Index:   uint64(18),
   823  	}
   824  
   825  	gotErr := unmarshalFailedKeysResponse(body)
   826  	if !reflect.DeepEqual(wantErr, gotErr) {
   827  		t.Errorf("unexpected error: want=%#v got=%#v", wantErr, gotErr)
   828  	}
   829  }
   830  
   831  func TestUnmarshalFailedKeysResponseBadJSON(t *testing.T) {
   832  	err := unmarshalFailedKeysResponse([]byte(`{"er`))
   833  	if err == nil {
   834  		t.Errorf("got nil error")
   835  	} else if _, ok := err.(Error); ok {
   836  		t.Errorf("error is of incorrect type *Error: %#v", err)
   837  	}
   838  }
   839  
   840  func TestHTTPWatcherNextWaitAction(t *testing.T) {
   841  	initAction := waitAction{
   842  		Prefix:    "/pants",
   843  		Key:       "/foo/bar",
   844  		Recursive: true,
   845  		WaitIndex: 19,
   846  	}
   847  
   848  	client := &actionAssertingHTTPClient{
   849  		t:   t,
   850  		act: &initAction,
   851  		resp: http.Response{
   852  			StatusCode: http.StatusOK,
   853  			Header:     http.Header{"X-Etcd-Index": []string{"42"}},
   854  		},
   855  		body: []byte(`{"action":"update","node":{"key":"/pants/foo/bar/baz","value":"snarf","modifiedIndex":21,"createdIndex":19},"prevNode":{"key":"/pants/foo/bar/baz","value":"snazz","modifiedIndex":20,"createdIndex":19}}`),
   856  	}
   857  
   858  	wantResponse := &Response{
   859  		Action:   "update",
   860  		Node:     &Node{Key: "/pants/foo/bar/baz", Value: "snarf", CreatedIndex: uint64(19), ModifiedIndex: uint64(21)},
   861  		PrevNode: &Node{Key: "/pants/foo/bar/baz", Value: "snazz", CreatedIndex: uint64(19), ModifiedIndex: uint64(20)},
   862  		Index:    uint64(42),
   863  	}
   864  
   865  	wantNextWait := waitAction{
   866  		Prefix:    "/pants",
   867  		Key:       "/foo/bar",
   868  		Recursive: true,
   869  		WaitIndex: 22,
   870  	}
   871  
   872  	watcher := &httpWatcher{
   873  		client:   client,
   874  		nextWait: initAction,
   875  	}
   876  
   877  	resp, err := watcher.Next(context.Background())
   878  	if err != nil {
   879  		t.Errorf("non-nil error: %#v", err)
   880  	}
   881  
   882  	if !reflect.DeepEqual(wantResponse, resp) {
   883  		t.Errorf("received incorrect Response: want=%#v got=%#v", wantResponse, resp)
   884  	}
   885  
   886  	if !reflect.DeepEqual(wantNextWait, watcher.nextWait) {
   887  		t.Errorf("nextWait incorrect: want=%#v got=%#v", wantNextWait, watcher.nextWait)
   888  	}
   889  }
   890  
   891  func TestHTTPWatcherNextFail(t *testing.T) {
   892  	tests := []httpClient{
   893  		// generic HTTP client failure
   894  		&staticHTTPClient{
   895  			err: errors.New("fail!"),
   896  		},
   897  
   898  		// unusable status code
   899  		&staticHTTPClient{
   900  			resp: http.Response{
   901  				StatusCode: http.StatusTeapot,
   902  			},
   903  		},
   904  
   905  		// etcd Error response
   906  		&staticHTTPClient{
   907  			resp: http.Response{
   908  				StatusCode: http.StatusNotFound,
   909  			},
   910  			body: []byte(`{"errorCode":100,"message":"Key not found","cause":"/foo","index":18}`),
   911  		},
   912  	}
   913  
   914  	for i, tt := range tests {
   915  		act := waitAction{
   916  			Prefix:    "/pants",
   917  			Key:       "/foo/bar",
   918  			Recursive: true,
   919  			WaitIndex: 19,
   920  		}
   921  
   922  		watcher := &httpWatcher{
   923  			client:   tt,
   924  			nextWait: act,
   925  		}
   926  
   927  		resp, err := watcher.Next(context.Background())
   928  		if err == nil {
   929  			t.Errorf("#%d: expected non-nil error", i)
   930  		}
   931  		if resp != nil {
   932  			t.Errorf("#%d: expected nil Response, got %#v", i, resp)
   933  		}
   934  		if !reflect.DeepEqual(act, watcher.nextWait) {
   935  			t.Errorf("#%d: nextWait changed: want=%#v got=%#v", i, act, watcher.nextWait)
   936  		}
   937  	}
   938  }
   939  
   940  func TestHTTPKeysAPIWatcherAction(t *testing.T) {
   941  	tests := []struct {
   942  		key  string
   943  		opts *WatcherOptions
   944  		want waitAction
   945  	}{
   946  		{
   947  			key:  "/foo",
   948  			opts: nil,
   949  			want: waitAction{
   950  				Key:       "/foo",
   951  				Recursive: false,
   952  				WaitIndex: 0,
   953  			},
   954  		},
   955  
   956  		{
   957  			key: "/foo",
   958  			opts: &WatcherOptions{
   959  				Recursive:  false,
   960  				AfterIndex: 0,
   961  			},
   962  			want: waitAction{
   963  				Key:       "/foo",
   964  				Recursive: false,
   965  				WaitIndex: 0,
   966  			},
   967  		},
   968  
   969  		{
   970  			key: "/foo",
   971  			opts: &WatcherOptions{
   972  				Recursive:  true,
   973  				AfterIndex: 0,
   974  			},
   975  			want: waitAction{
   976  				Key:       "/foo",
   977  				Recursive: true,
   978  				WaitIndex: 0,
   979  			},
   980  		},
   981  
   982  		{
   983  			key: "/foo",
   984  			opts: &WatcherOptions{
   985  				Recursive:  false,
   986  				AfterIndex: 19,
   987  			},
   988  			want: waitAction{
   989  				Key:       "/foo",
   990  				Recursive: false,
   991  				WaitIndex: 20,
   992  			},
   993  		},
   994  	}
   995  
   996  	for i, tt := range tests {
   997  		testError := errors.New("fail!")
   998  		kAPI := &httpKeysAPI{
   999  			client: &staticHTTPClient{err: testError},
  1000  		}
  1001  
  1002  		want := &httpWatcher{
  1003  			client:   &staticHTTPClient{err: testError},
  1004  			nextWait: tt.want,
  1005  		}
  1006  
  1007  		got := kAPI.Watcher(tt.key, tt.opts)
  1008  		if !reflect.DeepEqual(want, got) {
  1009  			t.Errorf("#%d: incorrect watcher: want=%#v got=%#v", i, want, got)
  1010  		}
  1011  	}
  1012  }
  1013  
  1014  func TestHTTPKeysAPISetAction(t *testing.T) {
  1015  	tests := []struct {
  1016  		key        string
  1017  		value      string
  1018  		opts       *SetOptions
  1019  		wantAction httpAction
  1020  	}{
  1021  		// nil SetOptions
  1022  		{
  1023  			key:   "/foo",
  1024  			value: "bar",
  1025  			opts:  nil,
  1026  			wantAction: &setAction{
  1027  				Key:       "/foo",
  1028  				Value:     "bar",
  1029  				PrevValue: "",
  1030  				PrevIndex: 0,
  1031  				PrevExist: PrevIgnore,
  1032  				TTL:       0,
  1033  			},
  1034  		},
  1035  		// empty SetOptions
  1036  		{
  1037  			key:   "/foo",
  1038  			value: "bar",
  1039  			opts:  &SetOptions{},
  1040  			wantAction: &setAction{
  1041  				Key:       "/foo",
  1042  				Value:     "bar",
  1043  				PrevValue: "",
  1044  				PrevIndex: 0,
  1045  				PrevExist: PrevIgnore,
  1046  				TTL:       0,
  1047  			},
  1048  		},
  1049  		// populated SetOptions
  1050  		{
  1051  			key:   "/foo",
  1052  			value: "bar",
  1053  			opts: &SetOptions{
  1054  				PrevValue: "baz",
  1055  				PrevIndex: 13,
  1056  				PrevExist: PrevExist,
  1057  				TTL:       time.Minute,
  1058  				Dir:       true,
  1059  			},
  1060  			wantAction: &setAction{
  1061  				Key:       "/foo",
  1062  				Value:     "bar",
  1063  				PrevValue: "baz",
  1064  				PrevIndex: 13,
  1065  				PrevExist: PrevExist,
  1066  				TTL:       time.Minute,
  1067  				Dir:       true,
  1068  			},
  1069  		},
  1070  	}
  1071  
  1072  	for i, tt := range tests {
  1073  		client := &actionAssertingHTTPClient{t: t, num: i, act: tt.wantAction}
  1074  		kAPI := httpKeysAPI{client: client}
  1075  		kAPI.Set(context.Background(), tt.key, tt.value, tt.opts)
  1076  	}
  1077  }
  1078  
  1079  func TestHTTPKeysAPISetError(t *testing.T) {
  1080  	tests := []httpClient{
  1081  		// generic HTTP client failure
  1082  		&staticHTTPClient{
  1083  			err: errors.New("fail!"),
  1084  		},
  1085  
  1086  		// unusable status code
  1087  		&staticHTTPClient{
  1088  			resp: http.Response{
  1089  				StatusCode: http.StatusTeapot,
  1090  			},
  1091  		},
  1092  
  1093  		// etcd Error response
  1094  		&staticHTTPClient{
  1095  			resp: http.Response{
  1096  				StatusCode: http.StatusInternalServerError,
  1097  			},
  1098  			body: []byte(`{"errorCode":300,"message":"Raft internal error","cause":"/foo","index":18}`),
  1099  		},
  1100  	}
  1101  
  1102  	for i, tt := range tests {
  1103  		kAPI := httpKeysAPI{client: tt}
  1104  		resp, err := kAPI.Set(context.Background(), "/foo", "bar", nil)
  1105  		if err == nil {
  1106  			t.Errorf("#%d: received nil error", i)
  1107  		}
  1108  		if resp != nil {
  1109  			t.Errorf("#%d: received non-nil Response: %#v", i, resp)
  1110  		}
  1111  	}
  1112  }
  1113  
  1114  func TestHTTPKeysAPISetResponse(t *testing.T) {
  1115  	client := &staticHTTPClient{
  1116  		resp: http.Response{
  1117  			StatusCode: http.StatusOK,
  1118  			Header:     http.Header{"X-Etcd-Index": []string{"21"}},
  1119  		},
  1120  		body: []byte(`{"action":"set","node":{"key":"/pants/foo/bar/baz","value":"snarf","modifiedIndex":21,"createdIndex":21},"prevNode":{"key":"/pants/foo/bar/baz","value":"snazz","modifiedIndex":20,"createdIndex":19}}`),
  1121  	}
  1122  
  1123  	wantResponse := &Response{
  1124  		Action:   "set",
  1125  		Node:     &Node{Key: "/pants/foo/bar/baz", Value: "snarf", CreatedIndex: uint64(21), ModifiedIndex: uint64(21)},
  1126  		PrevNode: &Node{Key: "/pants/foo/bar/baz", Value: "snazz", CreatedIndex: uint64(19), ModifiedIndex: uint64(20)},
  1127  		Index:    uint64(21),
  1128  	}
  1129  
  1130  	kAPI := &httpKeysAPI{client: client, prefix: "/pants"}
  1131  	resp, err := kAPI.Set(context.Background(), "/foo/bar/baz", "snarf", nil)
  1132  	if err != nil {
  1133  		t.Errorf("non-nil error: %#v", err)
  1134  	}
  1135  	if !reflect.DeepEqual(wantResponse, resp) {
  1136  		t.Errorf("incorrect Response: want=%#v got=%#v", wantResponse, resp)
  1137  	}
  1138  }
  1139  
  1140  func TestHTTPKeysAPIGetAction(t *testing.T) {
  1141  	tests := []struct {
  1142  		key        string
  1143  		opts       *GetOptions
  1144  		wantAction httpAction
  1145  	}{
  1146  		// nil GetOptions
  1147  		{
  1148  			key:  "/foo",
  1149  			opts: nil,
  1150  			wantAction: &getAction{
  1151  				Key:       "/foo",
  1152  				Sorted:    false,
  1153  				Recursive: false,
  1154  			},
  1155  		},
  1156  		// empty GetOptions
  1157  		{
  1158  			key:  "/foo",
  1159  			opts: &GetOptions{},
  1160  			wantAction: &getAction{
  1161  				Key:       "/foo",
  1162  				Sorted:    false,
  1163  				Recursive: false,
  1164  			},
  1165  		},
  1166  		// populated GetOptions
  1167  		{
  1168  			key: "/foo",
  1169  			opts: &GetOptions{
  1170  				Sort:      true,
  1171  				Recursive: true,
  1172  				Quorum:    true,
  1173  			},
  1174  			wantAction: &getAction{
  1175  				Key:       "/foo",
  1176  				Sorted:    true,
  1177  				Recursive: true,
  1178  				Quorum:    true,
  1179  			},
  1180  		},
  1181  	}
  1182  
  1183  	for i, tt := range tests {
  1184  		client := &actionAssertingHTTPClient{t: t, num: i, act: tt.wantAction}
  1185  		kAPI := httpKeysAPI{client: client}
  1186  		kAPI.Get(context.Background(), tt.key, tt.opts)
  1187  	}
  1188  }
  1189  
  1190  func TestHTTPKeysAPIGetError(t *testing.T) {
  1191  	tests := []httpClient{
  1192  		// generic HTTP client failure
  1193  		&staticHTTPClient{
  1194  			err: errors.New("fail!"),
  1195  		},
  1196  
  1197  		// unusable status code
  1198  		&staticHTTPClient{
  1199  			resp: http.Response{
  1200  				StatusCode: http.StatusTeapot,
  1201  			},
  1202  		},
  1203  
  1204  		// etcd Error response
  1205  		&staticHTTPClient{
  1206  			resp: http.Response{
  1207  				StatusCode: http.StatusInternalServerError,
  1208  			},
  1209  			body: []byte(`{"errorCode":300,"message":"Raft internal error","cause":"/foo","index":18}`),
  1210  		},
  1211  	}
  1212  
  1213  	for i, tt := range tests {
  1214  		kAPI := httpKeysAPI{client: tt}
  1215  		resp, err := kAPI.Get(context.Background(), "/foo", nil)
  1216  		if err == nil {
  1217  			t.Errorf("#%d: received nil error", i)
  1218  		}
  1219  		if resp != nil {
  1220  			t.Errorf("#%d: received non-nil Response: %#v", i, resp)
  1221  		}
  1222  	}
  1223  }
  1224  
  1225  func TestHTTPKeysAPIGetResponse(t *testing.T) {
  1226  	client := &staticHTTPClient{
  1227  		resp: http.Response{
  1228  			StatusCode: http.StatusOK,
  1229  			Header:     http.Header{"X-Etcd-Index": []string{"42"}},
  1230  		},
  1231  		body: []byte(`{"action":"get","node":{"key":"/pants/foo/bar","modifiedIndex":25,"createdIndex":19,"nodes":[{"key":"/pants/foo/bar/baz","value":"snarf","createdIndex":21,"modifiedIndex":25}]}}`),
  1232  	}
  1233  
  1234  	wantResponse := &Response{
  1235  		Action: "get",
  1236  		Node: &Node{
  1237  			Key: "/pants/foo/bar",
  1238  			Nodes: []*Node{
  1239  				{Key: "/pants/foo/bar/baz", Value: "snarf", CreatedIndex: 21, ModifiedIndex: 25},
  1240  			},
  1241  			CreatedIndex:  uint64(19),
  1242  			ModifiedIndex: uint64(25),
  1243  		},
  1244  		Index: uint64(42),
  1245  	}
  1246  
  1247  	kAPI := &httpKeysAPI{client: client, prefix: "/pants"}
  1248  	resp, err := kAPI.Get(context.Background(), "/foo/bar", &GetOptions{Recursive: true})
  1249  	if err != nil {
  1250  		t.Errorf("non-nil error: %#v", err)
  1251  	}
  1252  	if !reflect.DeepEqual(wantResponse, resp) {
  1253  		t.Errorf("incorrect Response: want=%#v got=%#v", wantResponse, resp)
  1254  	}
  1255  }
  1256  
  1257  func TestHTTPKeysAPIDeleteAction(t *testing.T) {
  1258  	tests := []struct {
  1259  		key        string
  1260  		opts       *DeleteOptions
  1261  		wantAction httpAction
  1262  	}{
  1263  		// nil DeleteOptions
  1264  		{
  1265  			key:  "/foo",
  1266  			opts: nil,
  1267  			wantAction: &deleteAction{
  1268  				Key:       "/foo",
  1269  				PrevValue: "",
  1270  				PrevIndex: 0,
  1271  				Recursive: false,
  1272  			},
  1273  		},
  1274  		// empty DeleteOptions
  1275  		{
  1276  			key:  "/foo",
  1277  			opts: &DeleteOptions{},
  1278  			wantAction: &deleteAction{
  1279  				Key:       "/foo",
  1280  				PrevValue: "",
  1281  				PrevIndex: 0,
  1282  				Recursive: false,
  1283  			},
  1284  		},
  1285  		// populated DeleteOptions
  1286  		{
  1287  			key: "/foo",
  1288  			opts: &DeleteOptions{
  1289  				PrevValue: "baz",
  1290  				PrevIndex: 13,
  1291  				Recursive: true,
  1292  			},
  1293  			wantAction: &deleteAction{
  1294  				Key:       "/foo",
  1295  				PrevValue: "baz",
  1296  				PrevIndex: 13,
  1297  				Recursive: true,
  1298  			},
  1299  		},
  1300  	}
  1301  
  1302  	for i, tt := range tests {
  1303  		client := &actionAssertingHTTPClient{t: t, num: i, act: tt.wantAction}
  1304  		kAPI := httpKeysAPI{client: client}
  1305  		kAPI.Delete(context.Background(), tt.key, tt.opts)
  1306  	}
  1307  }
  1308  
  1309  func TestHTTPKeysAPIDeleteError(t *testing.T) {
  1310  	tests := []httpClient{
  1311  		// generic HTTP client failure
  1312  		&staticHTTPClient{
  1313  			err: errors.New("fail!"),
  1314  		},
  1315  
  1316  		// unusable status code
  1317  		&staticHTTPClient{
  1318  			resp: http.Response{
  1319  				StatusCode: http.StatusTeapot,
  1320  			},
  1321  		},
  1322  
  1323  		// etcd Error response
  1324  		&staticHTTPClient{
  1325  			resp: http.Response{
  1326  				StatusCode: http.StatusInternalServerError,
  1327  			},
  1328  			body: []byte(`{"errorCode":300,"message":"Raft internal error","cause":"/foo","index":18}`),
  1329  		},
  1330  	}
  1331  
  1332  	for i, tt := range tests {
  1333  		kAPI := httpKeysAPI{client: tt}
  1334  		resp, err := kAPI.Delete(context.Background(), "/foo", nil)
  1335  		if err == nil {
  1336  			t.Errorf("#%d: received nil error", i)
  1337  		}
  1338  		if resp != nil {
  1339  			t.Errorf("#%d: received non-nil Response: %#v", i, resp)
  1340  		}
  1341  	}
  1342  }
  1343  
  1344  func TestHTTPKeysAPIDeleteResponse(t *testing.T) {
  1345  	client := &staticHTTPClient{
  1346  		resp: http.Response{
  1347  			StatusCode: http.StatusOK,
  1348  			Header:     http.Header{"X-Etcd-Index": []string{"22"}},
  1349  		},
  1350  		body: []byte(`{"action":"delete","node":{"key":"/pants/foo/bar/baz","value":"snarf","modifiedIndex":22,"createdIndex":19},"prevNode":{"key":"/pants/foo/bar/baz","value":"snazz","modifiedIndex":20,"createdIndex":19}}`),
  1351  	}
  1352  
  1353  	wantResponse := &Response{
  1354  		Action:   "delete",
  1355  		Node:     &Node{Key: "/pants/foo/bar/baz", Value: "snarf", CreatedIndex: uint64(19), ModifiedIndex: uint64(22)},
  1356  		PrevNode: &Node{Key: "/pants/foo/bar/baz", Value: "snazz", CreatedIndex: uint64(19), ModifiedIndex: uint64(20)},
  1357  		Index:    uint64(22),
  1358  	}
  1359  
  1360  	kAPI := &httpKeysAPI{client: client, prefix: "/pants"}
  1361  	resp, err := kAPI.Delete(context.Background(), "/foo/bar/baz", nil)
  1362  	if err != nil {
  1363  		t.Errorf("non-nil error: %#v", err)
  1364  	}
  1365  	if !reflect.DeepEqual(wantResponse, resp) {
  1366  		t.Errorf("incorrect Response: want=%#v got=%#v", wantResponse, resp)
  1367  	}
  1368  }
  1369  
  1370  func TestHTTPKeysAPICreateAction(t *testing.T) {
  1371  	act := &setAction{
  1372  		Key:       "/foo",
  1373  		Value:     "bar",
  1374  		PrevExist: PrevNoExist,
  1375  		PrevIndex: 0,
  1376  		PrevValue: "",
  1377  		TTL:       0,
  1378  	}
  1379  
  1380  	kAPI := httpKeysAPI{client: &actionAssertingHTTPClient{t: t, act: act}}
  1381  	kAPI.Create(context.Background(), "/foo", "bar")
  1382  }
  1383  
  1384  func TestHTTPKeysAPICreateInOrderAction(t *testing.T) {
  1385  	act := &createInOrderAction{
  1386  		Dir:   "/foo",
  1387  		Value: "bar",
  1388  		TTL:   0,
  1389  	}
  1390  	kAPI := httpKeysAPI{client: &actionAssertingHTTPClient{t: t, act: act}}
  1391  	kAPI.CreateInOrder(context.Background(), "/foo", "bar", nil)
  1392  }
  1393  
  1394  func TestHTTPKeysAPIUpdateAction(t *testing.T) {
  1395  	act := &setAction{
  1396  		Key:       "/foo",
  1397  		Value:     "bar",
  1398  		PrevExist: PrevExist,
  1399  		PrevIndex: 0,
  1400  		PrevValue: "",
  1401  		TTL:       0,
  1402  	}
  1403  
  1404  	kAPI := httpKeysAPI{client: &actionAssertingHTTPClient{t: t, act: act}}
  1405  	kAPI.Update(context.Background(), "/foo", "bar")
  1406  }
  1407  
  1408  func TestNodeTTLDuration(t *testing.T) {
  1409  	tests := []struct {
  1410  		node *Node
  1411  		want time.Duration
  1412  	}{
  1413  		{
  1414  			node: &Node{TTL: 0},
  1415  			want: 0,
  1416  		},
  1417  		{
  1418  			node: &Node{TTL: 97},
  1419  			want: 97 * time.Second,
  1420  		},
  1421  	}
  1422  
  1423  	for i, tt := range tests {
  1424  		got := tt.node.TTLDuration()
  1425  		if tt.want != got {
  1426  			t.Errorf("#%d: incorrect duration: want=%v got=%v", i, tt.want, got)
  1427  		}
  1428  	}
  1429  }
  1430  

View as plain text