github.com/go-kivik/kivik/v4@v4.3.2/x/fsdb/cdb/document_test.go (about) 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 cdb 14 15 import ( 16 "context" 17 "net/http" 18 "os" 19 "regexp" 20 "runtime" 21 "testing" 22 23 "gitlab.com/flimzy/testy" 24 25 "github.com/go-kivik/kivik/v4" 26 internal "github.com/go-kivik/kivik/v4/int/errors" 27 "github.com/go-kivik/kivik/v4/x/fsdb/filesystem" 28 ) 29 30 const isGopherJS117 = runtime.GOARCH == "js" 31 32 func TestDocumentPersist(t *testing.T) { 33 if isGopherJS117 { 34 t.Skip("Tests broken for GopherJS 1.17") 35 } 36 type tt struct { 37 path string 38 doc *Document 39 status int 40 err string 41 } 42 tests := testy.NewTable() 43 tests.Add("nil doc", func(t *testing.T) interface{} { 44 var tmpdir string 45 tests.Cleanup(testy.TempDir(t, &tmpdir)) 46 47 return tt{ 48 path: tmpdir, 49 status: http.StatusBadRequest, 50 err: "document has no revisions", 51 } 52 }) 53 tests.Add("no revs", func(t *testing.T) interface{} { 54 var tmpdir string 55 tests.Cleanup(testy.TempDir(t, &tmpdir)) 56 57 cdb := New(tmpdir, filesystem.Default()) 58 59 return tt{ 60 path: tmpdir, 61 doc: cdb.NewDocument("foo"), 62 status: http.StatusBadRequest, 63 err: "document has no revisions", 64 } 65 }) 66 tests.Add("new doc, one rev", func(t *testing.T) interface{} { 67 var tmpdir string 68 tests.Cleanup(testy.TempDir(t, &tmpdir)) 69 70 cdb := New(tmpdir, filesystem.Default()) 71 doc := cdb.NewDocument("foo") 72 rev, err := cdb.NewRevision(map[string]string{ 73 "value": "bar", 74 }) 75 if err != nil { 76 t.Fatal(err) 77 } 78 if _, err := doc.AddRevision(context.TODO(), rev, kivik.Params(nil)); err != nil { 79 t.Fatal(err) 80 } 81 82 return tt{ 83 path: tmpdir, 84 doc: doc, 85 } 86 }) 87 tests.Add("update existing doc", func(t *testing.T) interface{} { 88 tmpdir := testy.CopyTempDir(t, "testdata/persist.update", 0) 89 tests.Cleanup(func() error { 90 return os.RemoveAll(tmpdir) 91 }) 92 93 cdb := New(tmpdir) 94 doc, err := cdb.OpenDocID("foo", kivik.Params(nil)) 95 if err != nil { 96 t.Fatal(err) 97 } 98 rev, err := cdb.NewRevision(map[string]interface{}{ 99 "_rev": "1-xxx", 100 "value": "bar", 101 "_revisions": map[string]interface{}{ 102 "start": 2, 103 "ids": []string{"yyy", "xxx"}, 104 }, 105 }) 106 if err != nil { 107 t.Fatal(err) 108 } 109 if _, err := doc.AddRevision(context.TODO(), rev, kivik.Params(nil)); err != nil { 110 t.Fatal(err) 111 } 112 113 return tt{ 114 path: tmpdir, 115 doc: doc, 116 } 117 }) 118 tests.Add("update existing doc with attachments", func(t *testing.T) interface{} { 119 tmpdir := testy.CopyTempDir(t, "testdata/persist.att", 0) 120 tests.Cleanup(func() error { 121 return os.RemoveAll(tmpdir) 122 }) 123 124 cdb := New(tmpdir) 125 doc, err := cdb.OpenDocID("bar", kivik.Params(nil)) 126 if err != nil { 127 t.Fatal(err) 128 } 129 rev, err := cdb.NewRevision(map[string]interface{}{ 130 "_rev": "1-xxx", 131 "value": "bar", 132 "_revisions": map[string]interface{}{ 133 "start": 2, 134 "ids": []string{"yyy", "xxx"}, 135 }, 136 "_attachments": map[string]interface{}{ 137 "bar.txt": map[string]interface{}{ 138 "content_type": "text/plain", 139 "data": []byte("Additional content"), 140 }, 141 "foo.txt": map[string]interface{}{ 142 "content_type": "text/plain", 143 "stub": true, 144 }, 145 }, 146 }) 147 if err != nil { 148 t.Fatal(err) 149 } 150 if _, err := doc.addRevision(context.TODO(), rev, kivik.Params(nil)); err != nil { 151 t.Fatal(err) 152 } 153 154 return tt{ 155 path: tmpdir, 156 doc: doc, 157 } 158 }) 159 160 tests.Run(t, func(t *testing.T, tt tt) { 161 err := tt.doc.persist(context.TODO()) 162 if d := internal.StatusErrorDiff(tt.err, tt.status, err); d != "" { 163 t.Error(d) 164 } 165 if err != nil { 166 return 167 } 168 if d := testy.DiffInterface(testy.Snapshot(t), tt.doc, tmpdirRE(tt.path)); d != nil { 169 t.Error(d) 170 } 171 if d := testy.DiffAsJSON(testy.Snapshot(t, "fs"), testy.JSONDir{ 172 Path: tt.path, 173 NoMD5Sum: true, 174 FileContent: true, 175 }); d != nil { 176 t.Error(d) 177 } 178 }) 179 } 180 181 func tmpdirRE(path string) testy.Replacement { 182 return testy.Replacement{ 183 Regexp: regexp.MustCompile(`len=\d+\) "` + regexp.QuoteMeta(path)), 184 Replacement: `len=X) "<tmpdir>`, 185 } 186 } 187 188 func TestDocumentAddRevision(t *testing.T) { 189 if isGopherJS117 { 190 t.Skip("Tests broken for GopherJS 1.17") 191 } 192 193 type tt struct { 194 path string 195 doc *Document 196 rev *Revision 197 options kivik.Option 198 status int 199 err string 200 expected string 201 } 202 tests := testy.NewTable() 203 tests.Add("stub with bad digest", func(t *testing.T) interface{} { 204 tmpdir := testy.CopyTempDir(t, "testdata/persist.att", 0) 205 tests.Cleanup(func() error { 206 return os.RemoveAll(tmpdir) 207 }) 208 209 cdb := New(tmpdir) 210 doc, err := cdb.OpenDocID("bar", kivik.Params(nil)) 211 if err != nil { 212 t.Fatal(err) 213 } 214 rev, err := cdb.NewRevision(map[string]interface{}{ 215 "_rev": "1-xxx", 216 "value": "bar", 217 "_revisions": map[string]interface{}{ 218 "start": 2, 219 "ids": []string{"yyy", "xxx"}, 220 }, 221 "_attachments": map[string]interface{}{ 222 "foo.txt": map[string]interface{}{ 223 "content_type": "text/plain", 224 "stub": true, 225 "digest": "md5-asdf", 226 }, 227 }, 228 }) 229 if err != nil { 230 t.Fatal(err) 231 } 232 233 return tt{ 234 path: tmpdir, 235 doc: doc, 236 rev: rev, 237 status: http.StatusBadRequest, 238 err: "invalid attachment data for foo.txt", 239 } 240 }) 241 tests.Add("stub with wrong revpos", func(t *testing.T) interface{} { 242 tmpdir := testy.CopyTempDir(t, "testdata/persist.att", 0) 243 tests.Cleanup(func() error { 244 return os.RemoveAll(tmpdir) 245 }) 246 247 cdb := New(tmpdir) 248 doc, err := cdb.OpenDocID("bar", kivik.Params(nil)) 249 if err != nil { 250 t.Fatal(err) 251 } 252 rev, err := cdb.NewRevision(map[string]interface{}{ 253 "_rev": "1-xxx", 254 "value": "bar", 255 "_revisions": map[string]interface{}{ 256 "start": 2, 257 "ids": []string{"yyy", "xxx"}, 258 }, 259 "_attachments": map[string]interface{}{ 260 "foo.txt": map[string]interface{}{ 261 "content_type": "text/plain", 262 "stub": true, 263 "revpos": 6, 264 }, 265 }, 266 }) 267 if err != nil { 268 t.Fatal(err) 269 } 270 271 return tt{ 272 path: tmpdir, 273 doc: doc, 274 rev: rev, 275 status: http.StatusBadRequest, 276 err: "invalid attachment data for foo.txt", 277 } 278 }) 279 tests.Add("stub with 0 revpos", func(t *testing.T) interface{} { 280 tmpdir := testy.CopyTempDir(t, "testdata/persist.att", 0) 281 tests.Cleanup(func() error { 282 return os.RemoveAll(tmpdir) 283 }) 284 285 cdb := New(tmpdir) 286 doc, err := cdb.OpenDocID("bar", kivik.Params(nil)) 287 if err != nil { 288 t.Fatal(err) 289 } 290 rev, err := cdb.NewRevision(map[string]interface{}{ 291 "_rev": "1-xxx", 292 "value": "bar", 293 "_revisions": map[string]interface{}{ 294 "start": 2, 295 "ids": []string{"yyy", "xxx"}, 296 }, 297 "_attachments": map[string]interface{}{ 298 "foo.txt": map[string]interface{}{ 299 "content_type": "text/plain", 300 "stub": true, 301 "revpos": 0, 302 }, 303 }, 304 }) 305 if err != nil { 306 t.Fatal(err) 307 } 308 309 return tt{ 310 path: tmpdir, 311 doc: doc, 312 rev: rev, 313 status: http.StatusBadRequest, 314 err: "invalid attachment data for foo.txt", 315 } 316 }) 317 tests.Add("upload attachment", func(t *testing.T) interface{} { 318 var tmpdir string 319 tests.Cleanup(testy.TempDir(t, &tmpdir)) 320 321 cdb := New(tmpdir) 322 doc := cdb.NewDocument("foo") 323 rev, err := cdb.NewRevision(map[string]interface{}{ 324 "value": "bar", 325 "_revisions": map[string]interface{}{ 326 "start": 2, 327 "ids": []string{"yyy", "xxx"}, 328 }, 329 "_attachments": map[string]interface{}{ 330 "!foo.txt": map[string]interface{}{ 331 "content_type": "text/plain", 332 "data": []byte("some test content"), 333 }, 334 }, 335 }) 336 if err != nil { 337 t.Fatal(err) 338 } 339 340 return tt{ 341 path: tmpdir, 342 doc: doc, 343 rev: rev, 344 expected: "1-1472ad25836971f236294ad7b19d9f65", 345 } 346 }) 347 tests.Add("re-upload identical attachment", func(t *testing.T) interface{} { 348 tmpdir := testy.CopyTempDir(t, "testdata/persist.att", 0) 349 tests.Cleanup(func() error { 350 return os.RemoveAll(tmpdir) 351 }) 352 353 cdb := New(tmpdir) 354 doc, err := cdb.OpenDocID("bar", kivik.Params(nil)) 355 if err != nil { 356 t.Fatal(err) 357 } 358 rev, err := cdb.NewRevision(map[string]interface{}{ 359 "_rev": "1-xxx", 360 "_attachments": map[string]interface{}{ 361 "foo.txt": map[string]interface{}{ 362 "content_type": "text/plain", 363 "data": []byte("Test content\n"), 364 }, 365 }, 366 }) 367 if err != nil { 368 t.Fatal(err) 369 } 370 371 return tt{ 372 path: tmpdir, 373 doc: doc, 374 rev: rev, 375 expected: "2-61afc657ebc34041a2568f5d5ab9fc71", 376 } 377 }) 378 379 tests.Run(t, func(t *testing.T, tt tt) { 380 opts := tt.options 381 if opts == nil { 382 opts = kivik.Params(nil) 383 } 384 revid, err := tt.doc.addRevision(context.TODO(), tt.rev, opts) 385 if d := internal.StatusErrorDiff(tt.err, tt.status, err); d != "" { 386 t.Error(d) 387 } 388 if err != nil { 389 return 390 } 391 if revid != tt.expected { 392 t.Errorf("Unexpected revd: %s", revid) 393 } 394 if d := testy.DiffInterface(testy.Snapshot(t), tt.doc, tmpdirRE(tt.path)); d != nil { 395 t.Error(d) 396 } 397 if d := testy.DiffAsJSON(testy.Snapshot(t, "fs"), testy.JSONDir{ 398 Path: tt.path, 399 NoMD5Sum: true, 400 FileContent: true, 401 }); d != nil { 402 t.Error(d) 403 } 404 }) 405 }