...

Source file src/github.com/go-kivik/kivik/v4/replicate_test.go

Documentation: github.com/go-kivik/kivik/v4

     1  // Licensed under the Apache License, Version 2.0 (the "License"); you may not
     2  // use this file except in compliance with the License. You may obtain a copy of
     3  // the License at
     4  //
     5  //  http://www.apache.org/licenses/LICENSE-2.0
     6  //
     7  // Unless required by applicable law or agreed to in writing, software
     8  // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
     9  // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    10  // License for the specific language governing permissions and limitations under
    11  // the License.
    12  
    13  package kivik_test
    14  
    15  import (
    16  	"context"
    17  	"io"
    18  	"net/http"
    19  	"os"
    20  	"runtime"
    21  	"strings"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/google/go-cmp/cmp"
    26  	"gitlab.com/flimzy/testy"
    27  
    28  	"github.com/go-kivik/kivik/v4"
    29  	"github.com/go-kivik/kivik/v4/driver"
    30  	internal "github.com/go-kivik/kivik/v4/int/errors"
    31  	kivikmock "github.com/go-kivik/kivik/v4/mockdb"
    32  	_ "github.com/go-kivik/kivik/v4/x/fsdb" // The filesystem driver
    33  )
    34  
    35  const isGopherJS117 = runtime.GOARCH == "js"
    36  
    37  func TestReplicateMock(t *testing.T) {
    38  	type tt struct {
    39  		mockT, mockS   *kivikmock.Client
    40  		target, source *kivik.DB
    41  		options        kivik.Option
    42  		status         int
    43  		err            string
    44  		result         *kivik.ReplicationResult
    45  	}
    46  	tests := testy.NewTable()
    47  	tests.Add("no changes", func(t *testing.T) interface{} {
    48  		source, mock := kivikmock.NewT(t)
    49  		db := mock.NewDB()
    50  		mock.ExpectDB().WillReturn(db)
    51  		db.ExpectChanges().WillReturn(kivikmock.NewChanges())
    52  
    53  		return tt{
    54  			mockS:  mock,
    55  			source: source.DB("src"),
    56  			result: &kivik.ReplicationResult{},
    57  		}
    58  	})
    59  	tests.Add("up to date", func(t *testing.T) interface{} {
    60  		source, smock := kivikmock.NewT(t)
    61  		sdb := smock.NewDB()
    62  		smock.ExpectDB().WillReturn(sdb)
    63  		sdb.ExpectChanges().WillReturn(kivikmock.NewChanges().
    64  			AddChange(&driver.Change{
    65  				ID:      "foo",
    66  				Changes: []string{"2-7051cbe5c8faecd085a3fa619e6e6337"},
    67  				Seq:     "3-g1AAAAG3eJzLYWBg4MhgTmHgz8tPSTV0MDQy1zMAQsMcoARTIkOS_P___7MSGXAqSVIAkkn2IFUZzIkMuUAee5pRqnGiuXkKA2dpXkpqWmZeagpu_Q4g_fGEbEkAqaqH2sIItsXAyMjM2NgUUwdOU_JYgCRDA5ACGjQfn30QlQsgKvcjfGaQZmaUmmZClM8gZhyAmHGfsG0PICrBPmQC22ZqbGRqamyIqSsLAAArcXo",
    68  			}))
    69  
    70  		target, tmock := kivikmock.NewT(t)
    71  		tdb := tmock.NewDB()
    72  		tmock.ExpectDB().WillReturn(tdb)
    73  		tdb.ExpectRevsDiff().
    74  			WithRevLookup(map[string][]string{
    75  				"foo": {"2-7051cbe5c8faecd085a3fa619e6e6337"},
    76  			}).
    77  			WillReturn(kivikmock.NewRows())
    78  
    79  		return tt{
    80  			mockS:  smock,
    81  			mockT:  tmock,
    82  			source: source.DB("src"),
    83  			target: target.DB("tgt"),
    84  			result: &kivik.ReplicationResult{},
    85  		}
    86  	})
    87  	tests.Add("one update", func(t *testing.T) interface{} {
    88  		source, smock := kivikmock.NewT(t)
    89  		sdb := smock.NewDB()
    90  		smock.ExpectDB().WillReturn(sdb)
    91  		sdb.ExpectChanges().WillReturn(kivikmock.NewChanges().
    92  			AddChange(&driver.Change{
    93  				ID:      "foo",
    94  				Changes: []string{"2-7051cbe5c8faecd085a3fa619e6e6337"},
    95  				Seq:     "3-g1AAAAG3eJzLYWBg4MhgTmHgz8tPSTV0MDQy1zMAQsMcoARTIkOS_P___7MSGXAqSVIAkkn2IFUZzIkMuUAee5pRqnGiuXkKA2dpXkpqWmZeagpu_Q4g_fGEbEkAqaqH2sIItsXAyMjM2NgUUwdOU_JYgCRDA5ACGjQfn30QlQsgKvcjfGaQZmaUmmZClM8gZhyAmHGfsG0PICrBPmQC22ZqbGRqamyIqSsLAAArcXo",
    96  			}))
    97  
    98  		target, tmock := kivikmock.NewT(t)
    99  		tdb := tmock.NewDB()
   100  		tmock.ExpectDB().WillReturn(tdb)
   101  		tdb.ExpectRevsDiff().
   102  			WithRevLookup(map[string][]string{
   103  				"foo": {"2-7051cbe5c8faecd085a3fa619e6e6337"},
   104  			}).
   105  			WillReturn(kivikmock.NewRows().
   106  				AddRow(&driver.Row{
   107  					ID:    "foo",
   108  					Value: strings.NewReader(`{"missing":["2-7051cbe5c8faecd085a3fa619e6e6337"]}`),
   109  				}))
   110  		sdb.ExpectOpenRevs().WillReturnError(&internal.Error{Status: http.StatusNotImplemented})
   111  		sdb.ExpectGet().
   112  			WithDocID("foo").
   113  			WithOptions(kivik.Params(map[string]interface{}{
   114  				"rev":         "2-7051cbe5c8faecd085a3fa619e6e6337",
   115  				"revs":        true,
   116  				"attachments": true,
   117  			})).
   118  			WillReturn(&driver.Document{
   119  				Body: io.NopCloser(strings.NewReader(`{"_id":"foo","_rev":"2-7051cbe5c8faecd085a3fa619e6e6337","foo":"bar"}`)),
   120  			})
   121  		tdb.ExpectPut().
   122  			WithDocID("foo").
   123  			WithOptions(kivik.Param("new_edits", false)).
   124  			WillReturn("2-7051cbe5c8faecd085a3fa619e6e6337")
   125  
   126  		return tt{
   127  			mockS:  smock,
   128  			mockT:  tmock,
   129  			source: source.DB("src"),
   130  			target: target.DB("tgt"),
   131  			result: &kivik.ReplicationResult{
   132  				DocsRead:       1,
   133  				DocsWritten:    1,
   134  				MissingChecked: 1,
   135  				MissingFound:   1,
   136  			},
   137  		}
   138  	})
   139  	tests.Add("one update with OpenRevs", func(t *testing.T) interface{} {
   140  		source, smock := kivikmock.NewT(t)
   141  		sdb := smock.NewDB()
   142  		smock.ExpectDB().WillReturn(sdb)
   143  		sdb.ExpectChanges().WillReturn(kivikmock.NewChanges().
   144  			AddChange(&driver.Change{
   145  				ID:      "foo",
   146  				Changes: []string{"2-7051cbe5c8faecd085a3fa619e6e6337"},
   147  				Seq:     "3-g1AAAAG3eJzLYWBg4MhgTmHgz8tPSTV0MDQy1zMAQsMcoARTIkOS_P___7MSGXAqSVIAkkn2IFUZzIkMuUAee5pRqnGiuXkKA2dpXkpqWmZeagpu_Q4g_fGEbEkAqaqH2sIItsXAyMjM2NgUUwdOU_JYgCRDA5ACGjQfn30QlQsgKvcjfGaQZmaUmmZClM8gZhyAmHGfsG0PICrBPmQC22ZqbGRqamyIqSsLAAArcXo",
   148  			}))
   149  
   150  		target, tmock := kivikmock.NewT(t)
   151  		tdb := tmock.NewDB()
   152  		tmock.ExpectDB().WillReturn(tdb)
   153  		tdb.ExpectRevsDiff().
   154  			WithRevLookup(map[string][]string{
   155  				"foo": {"2-7051cbe5c8faecd085a3fa619e6e6337"},
   156  			}).
   157  			WillReturn(kivikmock.NewRows().
   158  				AddRow(&driver.Row{
   159  					ID:    "foo",
   160  					Value: strings.NewReader(`{"missing":["2-7051cbe5c8faecd085a3fa619e6e6337"]}`),
   161  				}))
   162  		sdb.ExpectOpenRevs().
   163  			WithDocID("foo").
   164  			WillReturn(kivikmock.NewRows().AddRow(&driver.Row{
   165  				ID:  "foo",
   166  				Rev: "2-7051cbe5c8faecd085a3fa619e6e6337",
   167  				Doc: strings.NewReader(`{"_id":"foo","_rev":"2-7051cbe5c8faecd085a3fa619e6e6337","foo":"bar"}`),
   168  			}))
   169  		tdb.ExpectPut().
   170  			WithDocID("foo").
   171  			WithOptions(kivik.Param("new_edits", false)).
   172  			WillReturn("2-7051cbe5c8faecd085a3fa619e6e6337")
   173  
   174  		return tt{
   175  			mockS:  smock,
   176  			mockT:  tmock,
   177  			source: source.DB("src"),
   178  			target: target.DB("tgt"),
   179  			result: &kivik.ReplicationResult{
   180  				DocsRead:       1,
   181  				DocsWritten:    1,
   182  				MissingChecked: 1,
   183  				MissingFound:   1,
   184  			},
   185  		}
   186  	})
   187  
   188  	tests.Run(t, func(t *testing.T, tt tt) {
   189  		result, err := kivik.Replicate(context.TODO(), tt.target, tt.source, tt.options)
   190  		if d := internal.StatusErrorDiff(tt.err, tt.status, err); d != "" {
   191  			t.Error(d)
   192  		}
   193  		if tt.mockT != nil {
   194  			if err := tt.mockT.ExpectationsWereMet(); !testy.ErrorMatches("", err) {
   195  				t.Errorf("Unexpected error: %s", err)
   196  			}
   197  		}
   198  		if tt.mockS != nil {
   199  			if err := tt.mockS.ExpectationsWereMet(); !testy.ErrorMatches("", err) {
   200  				t.Errorf("Unexpected error: %s", err)
   201  			}
   202  		}
   203  		result.StartTime = time.Time{}
   204  		result.EndTime = time.Time{}
   205  		if d := testy.DiffAsJSON(tt.result, result); d != nil {
   206  			t.Error(d)
   207  		}
   208  	})
   209  }
   210  
   211  func TestReplicate_with_callback(t *testing.T) {
   212  	source, smock := kivikmock.NewT(t)
   213  	sdb := smock.NewDB()
   214  	smock.ExpectDB().WillReturn(sdb)
   215  	sdb.ExpectChanges().WillReturn(kivikmock.NewChanges().
   216  		AddChange(&driver.Change{
   217  			ID:      "foo",
   218  			Changes: []string{"2-7051cbe5c8faecd085a3fa619e6e6337"},
   219  			Seq:     "3-g1AAAAG3eJzLYWBg4MhgTmHgz8tPSTV0MDQy1zMAQsMcoARTIkOS_P___7MSGXAqSVIAkkn2IFUZzIkMuUAee5pRqnGiuXkKA2dpXkpqWmZeagpu_Q4g_fGEbEkAqaqH2sIItsXAyMjM2NgUUwdOU_JYgCRDA5ACGjQfn30QlQsgKvcjfGaQZmaUmmZClM8gZhyAmHGfsG0PICrBPmQC22ZqbGRqamyIqSsLAAArcXo",
   220  		}))
   221  
   222  	target, tmock := kivikmock.NewT(t)
   223  	tdb := tmock.NewDB()
   224  	tmock.ExpectDB().WillReturn(tdb)
   225  	tdb.ExpectRevsDiff().
   226  		WithRevLookup(map[string][]string{
   227  			"foo": {"2-7051cbe5c8faecd085a3fa619e6e6337"},
   228  		}).
   229  		WillReturn(kivikmock.NewRows().
   230  			AddRow(&driver.Row{
   231  				ID:    "foo",
   232  				Value: strings.NewReader(`{"missing":["2-7051cbe5c8faecd085a3fa619e6e6337"]}`),
   233  			}))
   234  	sdb.ExpectOpenRevs().WillReturnError(&internal.Error{Status: http.StatusNotImplemented})
   235  	sdb.ExpectGet().
   236  		WithDocID("foo").
   237  		WithOptions(kivik.Params(map[string]interface{}{
   238  			"rev":         "2-7051cbe5c8faecd085a3fa619e6e6337",
   239  			"revs":        true,
   240  			"attachments": true,
   241  		})).
   242  		WillReturn(&driver.Document{
   243  			Body: io.NopCloser(strings.NewReader(`{"_id":"foo","_rev":"2-7051cbe5c8faecd085a3fa619e6e6337","foo":"bar"}`)),
   244  		})
   245  	tdb.ExpectPut().
   246  		WithDocID("foo").
   247  		WithOptions(kivik.Param("new_edits", false)).
   248  		WillReturn("2-7051cbe5c8faecd085a3fa619e6e6337")
   249  
   250  	events := []kivik.ReplicationEvent{}
   251  
   252  	_, err := kivik.Replicate(context.TODO(), target.DB("tgt"), source.DB("src"), kivik.ReplicateCallback(func(e kivik.ReplicationEvent) {
   253  		events = append(events, e)
   254  	}))
   255  	if err != nil {
   256  		t.Fatal(err)
   257  	}
   258  
   259  	expected := []kivik.ReplicationEvent{
   260  		{
   261  			Type: "changes",
   262  			Read: true,
   263  		},
   264  		{
   265  			Type:    "change",
   266  			Read:    true,
   267  			DocID:   "foo",
   268  			Changes: []string{"2-7051cbe5c8faecd085a3fa619e6e6337"},
   269  		},
   270  		{
   271  			Type: "revsdiff",
   272  			Read: true,
   273  		},
   274  		{
   275  			Type:  "revsdiff",
   276  			Read:  true,
   277  			DocID: "foo",
   278  		},
   279  		{
   280  			Type:  "document",
   281  			Read:  true,
   282  			DocID: "foo",
   283  		},
   284  		{
   285  			Type:  "document",
   286  			DocID: "foo",
   287  		},
   288  	}
   289  	if d := cmp.Diff(expected, events); d != "" {
   290  		t.Error(d)
   291  	}
   292  }
   293  
   294  func TestReplicate(t *testing.T) {
   295  	if isGopherJS117 {
   296  		t.Skip("Replication doesn't work in GopherJS 1.17")
   297  	}
   298  	type tt struct {
   299  		path           string
   300  		target, source *kivik.DB
   301  		options        kivik.Option
   302  		status         int
   303  		err            string
   304  	}
   305  	tests := testy.NewTable()
   306  	tests.Add("fs to fs", func(t *testing.T) interface{} {
   307  		tmpdir := testy.CopyTempDir(t, "testdata/db4", 1)
   308  		tests.Cleanup(func() error {
   309  			return os.RemoveAll(tmpdir)
   310  		})
   311  
   312  		client, err := kivik.New("fs", tmpdir)
   313  		if err != nil {
   314  			t.Fatal(err)
   315  		}
   316  		if err := client.CreateDB(context.TODO(), "target"); err != nil {
   317  			t.Fatal(err)
   318  		}
   319  
   320  		return tt{
   321  			path:   tmpdir,
   322  			target: client.DB("target"),
   323  			source: client.DB("db4"),
   324  		}
   325  	})
   326  
   327  	tests.Run(t, func(t *testing.T, tt tt) {
   328  		result, err := kivik.Replicate(context.TODO(), tt.target, tt.source, tt.options)
   329  		if d := internal.StatusErrorDiff(tt.err, tt.status, err); d != "" {
   330  			t.Error(d)
   331  		}
   332  		result.StartTime = time.Time{}
   333  		result.EndTime = time.Time{}
   334  		if d := testy.DiffAsJSON(testy.Snapshot(t), result); d != nil {
   335  			t.Error(d)
   336  		}
   337  		if d := testy.DiffAsJSON(testy.Snapshot(t, "fs"), testy.JSONDir{
   338  			Path:           tt.path,
   339  			FileContent:    true,
   340  			MaxContentSize: 100,
   341  		}); d != nil {
   342  			t.Error(d)
   343  		}
   344  	})
   345  }
   346  

View as plain text