github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/pkg/couchdb/couchdb_test.go (about) 1 package couchdb 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "reflect" 8 "strings" 9 "sync" 10 "testing" 11 "time" 12 "unsafe" 13 14 "github.com/cozy/cozy-stack/pkg/config/config" 15 "github.com/cozy/cozy-stack/pkg/couchdb/mango" 16 "github.com/cozy/cozy-stack/pkg/prefixer" 17 "github.com/cozy/cozy-stack/pkg/realtime" 18 "github.com/stretchr/testify/assert" 19 "github.com/stretchr/testify/require" 20 ) 21 22 const TestDoctype = "io.cozy.testobject" 23 24 var TestPrefix = prefixer.NewPrefixer(0, "test", "couchdb-tests") 25 var receivedEventsMutex sync.Mutex 26 var receivedEvents map[string]*realtime.Event 27 28 type testDoc struct { 29 TestID string `json:"_id,omitempty"` 30 TestRev string `json:"_rev,omitempty"` 31 Test string `json:"test"` 32 FieldA string `json:"fieldA,omitempty"` 33 FieldB int `json:"fieldB,omitempty"` 34 } 35 36 func TestCouchdb(t *testing.T) { 37 if testing.Short() { 38 t.Skip("a couchdb is required for this test: test skipped due to the use of --short flag") 39 } 40 41 config.UseTestFile(t) 42 43 if _, err := CheckStatus(context.Background()); err != nil { 44 require.NoError(t, err, "This test need couchdb to run.") 45 } 46 47 require.NoError(t, ResetDB(TestPrefix, TestDoctype)) 48 49 receivedEvents = make(map[string]*realtime.Event) 50 eventChan := realtime.GetHub().Subscriber(TestPrefix) 51 eventChan.Subscribe(TestDoctype) 52 go func() { 53 for ev := range eventChan.Channel { 54 receivedEventsMutex.Lock() 55 receivedEvents[ev.Verb+ev.Doc.ID()] = ev 56 receivedEventsMutex.Unlock() 57 } 58 }() 59 60 t.Cleanup(eventChan.Close) 61 t.Cleanup(func() { _ = DeleteDB(TestPrefix, TestDoctype) }) 62 63 t.Run("Errors", func(t *testing.T) { 64 err := Error{StatusCode: 404, Name: "not_found", Reason: "missing"} 65 assert.Contains(t, err.Error(), "not_found") 66 assert.Contains(t, err.Error(), "missing") 67 }) 68 69 t.Run("CreateDoc", func(t *testing.T) { 70 var err error 71 72 doc := makeTestDoc() 73 assert.Empty(t, doc.Rev(), doc.ID()) 74 75 // Create the document 76 err = CreateDoc(TestPrefix, doc) 77 assert.NoError(t, err) 78 assert.NotEmpty(t, doc.Rev(), doc.ID()) 79 80 docType, id := doc.DocType(), doc.ID() 81 assertGotEvent(t, realtime.EventCreate, doc.ID()) 82 83 // Fetch it and see if its match 84 fetched := &testDoc{} 85 err = GetDoc(TestPrefix, docType, id, fetched) 86 assert.NoError(t, err) 87 assert.Equal(t, doc.ID(), fetched.ID()) 88 assert.Equal(t, doc.Rev(), fetched.Rev()) 89 assert.Equal(t, "somevalue", fetched.Test) 90 91 revBackup := fetched.Rev() 92 93 // Update it 94 updated := fetched 95 updated.Test = "changedvalue" 96 err = UpdateDoc(TestPrefix, updated) 97 assert.NoError(t, err) 98 assert.NotEqual(t, revBackup, updated.Rev()) 99 assert.Equal(t, "changedvalue", updated.Test) 100 evt := assertGotEvent(t, realtime.EventUpdate, doc.ID()) 101 assert.NotNil(t, evt.OldDoc) 102 assert.Equal(t, "somevalue", evt.OldDoc.(*testDoc).Test) 103 assert.Equal(t, "changedvalue", evt.Doc.(*testDoc).Test) 104 105 // Refetch it and see if its match 106 fetched2 := &testDoc{} 107 err = GetDoc(TestPrefix, docType, id, fetched2) 108 assert.NoError(t, err) 109 assert.Equal(t, doc.ID(), fetched2.ID()) 110 assert.Equal(t, updated.Rev(), fetched2.Rev()) 111 assert.Equal(t, "changedvalue", fetched2.Test) 112 113 // Delete it 114 err = DeleteDoc(TestPrefix, updated) 115 assert.NoError(t, err) 116 assertGotEvent(t, realtime.EventDelete, doc.ID()) 117 118 fetched3 := &testDoc{} 119 err = GetDoc(TestPrefix, docType, id, fetched3) 120 assert.Error(t, err) 121 coucherr, iscoucherr := err.(*Error) 122 if assert.True(t, iscoucherr) { 123 assert.Equal(t, coucherr.Reason, "deleted") 124 } 125 }) 126 127 t.Run("GetAllDocs", func(t *testing.T) { 128 doc1 := &testDoc{Test: "all_1"} 129 doc2 := &testDoc{Test: "all_2"} 130 assert.NoError(t, CreateDoc(TestPrefix, doc1)) 131 assert.NoError(t, CreateDoc(TestPrefix, doc2)) 132 133 var results []*testDoc 134 err := GetAllDocs(TestPrefix, TestDoctype, &AllDocsRequest{Limit: 2}, &results) 135 if assert.NoError(t, err) { 136 assert.Len(t, results, 2) 137 assert.Equal(t, results[0].Test, "all_1") 138 assert.Equal(t, results[1].Test, "all_2") 139 } 140 }) 141 142 t.Run("GetDocRevs", func(t *testing.T) { 143 doc := &testDoc{Test: "1"} 144 assert.NoError(t, CreateDoc(TestPrefix, doc)) 145 rev1 := doc.TestRev 146 doc.Test = "2" 147 assert.NoError(t, UpdateDoc(TestPrefix, doc)) 148 rev2 := doc.TestRev 149 150 res := &JSONDoc{} 151 err := GetDocWithRevs(TestPrefix, TestDoctype, doc.TestID, res) 152 revisions := res.M["_revisions"].(map[string]interface{}) 153 ids := revisions["ids"].([]interface{}) 154 assert.NoError(t, err) 155 assert.Len(t, ids, 2) 156 sliceRev1 := strings.SplitN(rev1, "-", 2) 157 assert.Len(t, sliceRev1, 2) 158 sliceRev2 := strings.SplitN(rev2, "-", 2) 159 assert.Len(t, sliceRev2, 2) 160 161 assert.Equal(t, ids[0].(string), sliceRev2[1]) 162 assert.Equal(t, ids[1].(string), sliceRev1[1]) 163 }) 164 165 t.Run("BulkUpdateDocs", func(t *testing.T) { 166 doc1 := &testDoc{Test: "before_1"} 167 doc2 := &testDoc{Test: "before_2"} 168 assert.NoError(t, CreateDoc(TestPrefix, doc1)) 169 assert.NoError(t, CreateDoc(TestPrefix, doc2)) 170 171 var results []*testDoc 172 err := GetAllDocs(TestPrefix, TestDoctype, &AllDocsRequest{Limit: 2}, &results) 173 assert.NoError(t, err) 174 results[0].Test = "after_1" 175 results[1].Test = "after_2" 176 177 olddocs := make([]interface{}, len(results)) 178 docs := make([]interface{}, len(results)) 179 for i, doc := range results { 180 docs[i] = doc 181 } 182 err = BulkUpdateDocs(TestPrefix, results[0].DocType(), docs, olddocs) 183 assert.NoError(t, err) 184 185 err = GetAllDocs(TestPrefix, TestDoctype, &AllDocsRequest{Limit: 2}, &results) 186 if assert.NoError(t, err) { 187 assert.Len(t, results, 2) 188 assert.Equal(t, results[0].Test, "after_1") 189 assert.Equal(t, results[1].Test, "after_2") 190 } 191 }) 192 193 t.Run("DefineIndex", func(t *testing.T) { 194 err := DefineIndex(TestPrefix, mango.MakeIndex(TestDoctype, "my-index", mango.IndexDef{Fields: []string{"fieldA", "fieldB"}})) 195 assert.NoError(t, err) 196 197 // if I try to define the same index several time 198 err2 := DefineIndex(TestPrefix, mango.MakeIndex(TestDoctype, "my-index", mango.IndexDef{Fields: []string{"fieldA", "fieldB"}})) 199 assert.NoError(t, err2) 200 }) 201 202 t.Run("DefineIndexWithPartialFilter", func(t *testing.T) { 203 err := DefineIndex(TestPrefix, mango.MakeIndex(TestDoctype, "my-index-with-partial-filter", mango.IndexDef{Fields: []string{"fieldA"}, PartialFilter: mango.NotExists("fieldB")})) 204 assert.NoError(t, err) 205 206 // if I try to define the same index several time 207 err2 := DefineIndex(TestPrefix, mango.MakeIndex(TestDoctype, "my-index-with-partial-filter", mango.IndexDef{Fields: []string{"fieldA"}, PartialFilter: mango.NotExists("fieldB")})) 208 assert.NoError(t, err2) 209 }) 210 211 t.Run("Query", func(t *testing.T) { 212 // create a few docs for testing 213 doc1 := testDoc{FieldA: "value1", FieldB: 100} 214 doc2 := testDoc{FieldA: "value2", FieldB: 1000} 215 doc3 := testDoc{FieldA: "value2", FieldB: 300} 216 doc4 := testDoc{FieldA: "value1", FieldB: 1500} 217 doc5 := testDoc{FieldA: "value1", FieldB: 150} 218 docs := []*testDoc{&doc1, &doc2, &doc3, &doc4, &doc5} 219 for _, doc := range docs { 220 err := CreateDoc(TestPrefix, doc) 221 if !assert.NoError(t, err) || doc.ID() == "" { 222 t.FailNow() 223 return 224 } 225 } 226 227 err := DefineIndex(TestPrefix, mango.MakeIndex(TestDoctype, "my-index", mango.IndexDef{Fields: []string{"fieldA", "fieldB"}})) 228 if !assert.NoError(t, err) { 229 t.FailNow() 230 return 231 } 232 var out []testDoc 233 req := &FindRequest{ 234 UseIndex: "my-index", 235 Selector: mango.And( 236 mango.Equal("fieldA", "value2"), 237 mango.Exists("fieldB"), 238 ), 239 } 240 err = FindDocs(TestPrefix, TestDoctype, req, &out) 241 if assert.NoError(t, err) { 242 assert.Len(t, out, 2, "should get 2 results") 243 // if fieldA are equaly, docs will be ordered by fieldB 244 assert.Equal(t, doc3.ID(), out[0].ID()) 245 assert.Equal(t, "value2", out[0].FieldA) 246 assert.Equal(t, doc2.ID(), out[1].ID()) 247 assert.Equal(t, "value2", out[1].FieldA) 248 } 249 250 var out2 []testDoc 251 req2 := &FindRequest{ 252 UseIndex: "my-index", 253 Selector: mango.And( 254 mango.Equal("fieldA", "value1"), 255 mango.Between("fieldB", 10, 1000), 256 ), 257 } 258 err = FindDocs(TestPrefix, TestDoctype, req2, &out2) 259 if assert.NoError(t, err) { 260 assert.Len(t, out, 2, "should get 2 results") 261 assert.Equal(t, doc1.ID(), out2[0].ID()) 262 assert.Equal(t, doc5.ID(), out2[1].ID()) 263 } 264 }) 265 266 t.Run("ForeachDocs", func(t *testing.T) { 267 for i := 0; i < 5; i++ { 268 doc := &testDoc{Test: fmt.Sprintf("foreach_%d", i)} 269 require.NoError(t, CreateDoc(TestPrefix, doc)) 270 } 271 272 var results []*testDoc 273 err := GetAllDocs(TestPrefix, TestDoctype, &AllDocsRequest{}, &results) 274 require.NoError(t, err) 275 var expected []string 276 for _, result := range results { 277 expected = append(expected, result.Test) 278 } 279 280 var keys []string 281 ForeachDocsWithCustomPagination(TestPrefix, TestDoctype, 2, func(id string, raw json.RawMessage) error { 282 var doc testDoc 283 err := json.Unmarshal(raw, &doc) 284 if err != nil { 285 return err 286 } 287 keys = append(keys, doc.Test) 288 return nil 289 }) 290 291 assert.Equal(t, expected, keys) 292 }) 293 294 t.Run("ChangesSuccess", func(t *testing.T) { 295 err := ResetDB(TestPrefix, TestDoctype) 296 assert.NoError(t, err) 297 298 request := &ChangesRequest{ 299 DocType: TestDoctype, 300 } 301 response, err := GetChanges(TestPrefix, request) 302 seqnoAfterCreates := response.LastSeq 303 assert.NoError(t, err) 304 assert.Len(t, response.Results, 0) 305 306 doc1 := makeTestDoc() 307 doc2 := makeTestDoc() 308 doc3 := makeTestDoc() 309 assert.NoError(t, CreateDoc(TestPrefix, doc1)) 310 assert.NoError(t, CreateDoc(TestPrefix, doc2)) 311 assert.NoError(t, CreateDoc(TestPrefix, doc3)) 312 313 request = &ChangesRequest{ 314 DocType: TestDoctype, 315 Since: seqnoAfterCreates, 316 } 317 318 response, err = GetChanges(TestPrefix, request) 319 assert.NoError(t, err) 320 assert.Len(t, response.Results, 3) 321 322 request = &ChangesRequest{ 323 DocType: TestDoctype, 324 Since: seqnoAfterCreates, 325 Limit: 2, 326 } 327 328 response, err = GetChanges(TestPrefix, request) 329 assert.NoError(t, err) 330 assert.Len(t, response.Results, 2) 331 332 seqnoAfterCreates = response.LastSeq 333 334 doc4 := makeTestDoc() 335 assert.NoError(t, CreateDoc(TestPrefix, doc4)) 336 337 request = &ChangesRequest{ 338 DocType: TestDoctype, 339 Since: seqnoAfterCreates, 340 } 341 response, err = GetChanges(TestPrefix, request) 342 assert.NoError(t, err) 343 assert.Len(t, response.Results, 2) 344 }) 345 346 t.Run("EnsureDBExist", func(t *testing.T) { 347 defer func() { _ = DeleteDB(TestPrefix, "io.cozy.tests.db1") }() 348 _, err := DBStatus(TestPrefix, "io.cozy.tests.db1") 349 assert.True(t, IsNoDatabaseError(err)) 350 assert.NoError(t, EnsureDBExist(TestPrefix, "io.cozy.tests.db1")) 351 _, err = DBStatus(TestPrefix, "io.cozy.tests.db1") 352 assert.NoError(t, err) 353 }) 354 355 t.Run("UpdateJSONDoc", func(t *testing.T) { 356 var err error 357 358 doc := &JSONDoc{ 359 Type: TestDoctype, 360 M: map[string]interface{}{ 361 "test": "1", 362 }, 363 } 364 assert.Empty(t, doc.Rev(), doc.ID()) 365 366 // Create the document 367 err = CreateDoc(TestPrefix, doc) 368 assert.NoError(t, err) 369 assert.NotEmpty(t, doc.Rev(), doc.ID()) 370 assertGotEvent(t, realtime.EventCreate, doc.ID()) 371 372 // Update it 373 updated := &JSONDoc{ 374 Type: TestDoctype, 375 M: map[string]interface{}{ 376 "_id": doc.ID(), 377 "_rev": doc.Rev(), 378 "test": "2", 379 }, 380 } 381 err = UpdateDoc(TestPrefix, updated) 382 assert.NoError(t, err) 383 assert.NotEqual(t, doc.Rev(), updated.Rev()) 384 assert.Equal(t, "2", updated.M["test"]) 385 evt := assertGotEvent(t, realtime.EventUpdate, doc.ID()) 386 assert.NotNil(t, evt) 387 assert.NotNil(t, evt.OldDoc) 388 assert.Equal(t, "1", evt.OldDoc.(*JSONDoc).M["test"]) 389 assert.Equal(t, "2", evt.Doc.(*JSONDoc).M["test"]) 390 391 // Remove the test field 392 noTest := &JSONDoc{ 393 Type: TestDoctype, 394 M: map[string]interface{}{ 395 "_id": updated.ID(), 396 "_rev": updated.Rev(), 397 "foo": "bar", 398 }, 399 } 400 err = UpdateDoc(TestPrefix, noTest) 401 assert.NoError(t, err) 402 assert.NotEqual(t, updated.Rev(), noTest.Rev()) 403 assert.Empty(t, noTest.M["test"]) 404 evt = assertGotEvent(t, realtime.EventUpdate, doc.ID()) 405 assert.NotNil(t, evt) 406 assert.NotNil(t, evt.OldDoc) 407 assert.Equal(t, "2", evt.OldDoc.(*JSONDoc).M["test"]) 408 assert.Empty(t, evt.Doc.(*JSONDoc).M["test"]) 409 410 // Add the test field 411 withTest := &JSONDoc{ 412 Type: TestDoctype, 413 M: map[string]interface{}{ 414 "_id": noTest.ID(), 415 "_rev": noTest.Rev(), 416 "foo": "baz", 417 "test": "3", 418 }, 419 } 420 err = UpdateDoc(TestPrefix, withTest) 421 assert.NoError(t, err) 422 assert.NotEqual(t, noTest.Rev(), withTest.Rev()) 423 assert.Equal(t, "3", withTest.M["test"]) 424 evt = assertGotEvent(t, realtime.EventUpdate, doc.ID()) 425 assert.NotNil(t, evt) 426 assert.NotNil(t, evt.OldDoc) 427 assert.Empty(t, evt.OldDoc.(*JSONDoc).M["test"]) 428 assert.Equal(t, "3", evt.Doc.(*JSONDoc).M["test"]) 429 }) 430 431 t.Run("JSONDocClone", func(t *testing.T) { 432 var m map[string]interface{} 433 data := []byte(`{ 434 "foo1": "bar", 435 "foo2": [0,1,2,3], 436 "foo3": ["abc", 1, 1.1], 437 "foo4": { 438 "bar1":"bar", 439 "bar2": [0,1,2,3], 440 "bar3": ["abc", 1, 1.1, { "key": "value", "key2": [{}, 1, 2, 3] }], 441 "bar4": {} 442 }, 443 "foo5": 1, 444 "foo6": 0.001, 445 "foo7": "toto" 446 }`) 447 448 err := json.Unmarshal(data, &m) 449 assert.NoError(t, err) 450 j1 := JSONDoc{ 451 Type: "toto", 452 M: m, 453 } 454 j2 := j1.Clone().(*JSONDoc) 455 456 assert.Equal(t, j1.Type, j2.Type) 457 assert.True(t, reflect.DeepEqual(j1.M, j2.M)) 458 459 assert.False(t, reflect.ValueOf(j1.M["foo2"]).Pointer() == reflect.ValueOf(j2.M["foo2"]).Pointer()) 460 assert.False(t, reflect.ValueOf(j1.M["foo3"]).Pointer() == reflect.ValueOf(j2.M["foo3"]).Pointer()) 461 assert.False(t, reflect.ValueOf(j1.M["foo4"]).Pointer() == reflect.ValueOf(j2.M["foo4"]).Pointer()) 462 463 s1 := j1.M["foo1"].(string) 464 s2 := j2.M["foo1"].(string) 465 s3 := j1.M["foo7"].(string) 466 s4 := j2.M["foo7"].(string) 467 468 hdr1 := (*reflect.StringHeader)(unsafe.Pointer(&s1)) 469 hdr2 := (*reflect.StringHeader)(unsafe.Pointer(&s2)) 470 hdr3 := (*reflect.StringHeader)(unsafe.Pointer(&s3)) 471 hdr4 := (*reflect.StringHeader)(unsafe.Pointer(&s4)) 472 473 assert.Equal(t, hdr1.Data, hdr2.Data) 474 assert.Equal(t, hdr1.Len, hdr2.Len) 475 476 assert.Equal(t, hdr3.Data, hdr4.Data) 477 assert.Equal(t, hdr3.Len, hdr4.Len) 478 479 assert.NotEqual(t, hdr1.Data, hdr4.Data) 480 assert.NotEqual(t, hdr1.Len, hdr4.Len) 481 }) 482 483 t.Run("LocalDocuments", func(t *testing.T) { 484 id := "foo" 485 _, err := GetLocal(TestPrefix, TestDoctype, id) 486 assert.True(t, IsNotFoundError(err)) 487 488 doc := map[string]interface{}{"bar": "baz"} 489 err = PutLocal(TestPrefix, TestDoctype, id, doc) 490 assert.NoError(t, err) 491 assert.NotEmpty(t, doc["_rev"]) 492 493 out, err := GetLocal(TestPrefix, TestDoctype, id) 494 assert.NoError(t, err) 495 assert.Equal(t, "baz", out["bar"]) 496 497 err = DeleteLocal(TestPrefix, TestDoctype, id) 498 assert.NoError(t, err) 499 500 _, err = GetLocal(TestPrefix, TestDoctype, id) 501 assert.True(t, IsNotFoundError(err)) 502 }) 503 } 504 505 func (t *testDoc) ID() string { 506 return t.TestID 507 } 508 509 func (t *testDoc) Rev() string { 510 return t.TestRev 511 } 512 513 func (t *testDoc) DocType() string { 514 return TestDoctype 515 } 516 517 func (t *testDoc) Clone() Doc { 518 cloned := *t 519 return &cloned 520 } 521 522 func (t *testDoc) SetID(id string) { 523 t.TestID = id 524 } 525 526 func (t *testDoc) SetRev(rev string) { 527 t.TestRev = rev 528 } 529 530 func makeTestDoc() Doc { 531 return &testDoc{ 532 Test: "somevalue", 533 } 534 } 535 536 func assertGotEvent(t *testing.T, eventType, id string) *realtime.Event { 537 t.Helper() 538 539 var event *realtime.Event 540 var ok bool 541 542 for i := 0; i < 200; i++ { 543 receivedEventsMutex.Lock() 544 event, ok = receivedEvents[eventType+id] 545 receivedEventsMutex.Unlock() 546 if ok { 547 delete(receivedEvents, eventType+id) 548 break 549 } 550 time.Sleep(time.Millisecond) 551 } 552 553 assert.True(t, ok, "Expected event %s:%s", eventType, id) 554 return event 555 }