// Unit test for bug #314 func TestChangesAfterChannelAdded(t *testing.T) { base.LogKeys["Cache"] = true base.LogKeys["Changes"] = true base.LogKeys["Changes+"] = true db := setupTestDB(t) defer tearDownTestDB(t, db) db.ChannelMapper = channels.NewDefaultChannelMapper() // Create a user with access to channel ABC authenticator := db.Authenticator() user, _ := authenticator.NewUser("naomi", "letmein", channels.SetOf("ABC")) authenticator.Save(user) // Create a doc on two channels (sequence 1): revid, _ := db.Put("doc1", Body{"channels": []string{"ABC", "PBS"}}) // Modify user to have access to both channels (sequence 2): userInfo, err := db.GetPrincipal("naomi", true) assert.True(t, userInfo != nil) userInfo.ExplicitChannels = base.SetOf("ABC", "PBS") _, err = db.UpdatePrincipal(*userInfo, true, true) assertNoError(t, err, "UpdatePrincipal failed") // Check the _changes feed: db.changeCache.waitForSequence(1) db.user, _ = authenticator.GetUser("naomi") changes, err := db.GetChanges(base.SetOf("*"), ChangesOptions{Since: SequenceID{Seq: 1}}) assertNoError(t, err, "Couldn't GetChanges") assert.Equals(t, len(changes), 2) assert.DeepEquals(t, changes[0], &ChangeEntry{ Seq: SequenceID{Seq: 1, TriggeredBy: 2}, ID: "doc1", Changes: []ChangeRev{{"rev": revid}}}) assert.DeepEquals(t, changes[1], &ChangeEntry{ Seq: SequenceID{Seq: 2}, ID: "_user/naomi", Changes: []ChangeRev{}}) // Add a new doc (sequence 3): revid, _ = db.Put("doc2", Body{"channels": []string{"PBS"}}) // Check the _changes feed -- this is to make sure the changeCache properly received // sequence 2 (the user doc) and isn't stuck waiting for it. db.changeCache.waitForSequence(3) changes, err = db.GetChanges(base.SetOf("*"), ChangesOptions{Since: SequenceID{Seq: 2}}) assertNoError(t, err, "Couldn't GetChanges (2nd)") assert.Equals(t, len(changes), 1) assert.DeepEquals(t, changes[0], &ChangeEntry{ Seq: SequenceID{Seq: 3}, ID: "doc2", Changes: []ChangeRev{{"rev": revid}}}) }
func TestRebuildUserRoles(t *testing.T) { computer := mockComputer{roles: ch.AtSequence(base.SetOf("role1", "role2"), 3)} auth := NewAuthenticator(gTestBucket, &computer) user, _ := auth.NewUser("testUser", "letmein", nil) user.SetExplicitRoles(ch.TimedSet{"role3": 1, "role1": 1}) err := auth.InvalidateRoles(user) assert.Equals(t, err, nil) user2, err := auth.GetUser("testUser") assert.Equals(t, err, nil) expected := ch.AtSequence(base.SetOf("role1", "role3"), 1) expected.AddChannel("role2", 3) assert.DeepEquals(t, user2.RoleNames(), expected) }
func TestSyncFnOnPush(t *testing.T) { db := setupTestDB(t) defer tearDownTestDB(t, db) db.ChannelMapper = channels.NewChannelMapper(`function(doc, oldDoc) { log("doc _id = "+doc._id+", _rev = "+doc._rev); if (oldDoc) log("oldDoc _id = "+oldDoc._id+", _rev = "+oldDoc._rev); channel(doc.channels); }`) // Create first revision: body := Body{"key1": "value1", "key2": 1234, "channels": []string{"public"}} rev1id, err := db.Put("doc1", body) assertNoError(t, err, "Couldn't create document") // Add several revisions at once to a doc, as on a push: log.Printf("Check PutExistingRev...") body["_rev"] = "4-four" body["key1"] = "fourth value" body["key2"] = int64(4444) body["channels"] = "clibup" history := []string{"4-four", "3-three", "2-488724414d0ed6b398d6d2aeb228d797", rev1id} err = db.PutExistingRev("doc1", body, history) assertNoError(t, err, "PutExistingRev failed") // Check that the doc has the correct channel (test for issue #300) doc, err := db.GetDoc("doc1") assert.DeepEquals(t, doc.Channels, channels.ChannelMap{ "clibup": nil, // i.e. it is currently in this channel (no removal) "public": &channels.ChannelRemoval{Seq: 2, RevID: "4-four"}, }) assert.DeepEquals(t, doc.History["4-four"].Channels, base.SetOf("clibup")) }
func (rt *restTester) setAdminParty(partyTime bool) { a := rt.ServerContext().Database("db").Authenticator() guest, _ := a.GetUser("") guest.SetDisabled(!partyTime) var chans channels.TimedSet if partyTime { chans = channels.AtSequence(base.SetOf("*"), 1) } guest.SetExplicitChannels(chans) a.Save(guest) }
// Creates a new Set from an array of strings. Returns an error if any names are invalid. func SetFromArray(names []string, mode StarMode) (base.Set, error) { for _, name := range names { if !IsValidChannel(name) { return nil, illegalChannelError(name) } } result := base.SetFromArray(names) switch mode { case RemoveStar: result = result.Removing("*") case ExpandStar: if result.Contains("*") { result = base.SetOf("*") } } return result, nil }
// Starts a changeListener on a given Bucket. func (listener *changeListener) Start(bucket base.Bucket, trackDocs bool) error { listener.bucket = bucket tapFeed, err := bucket.StartTapFeed(walrus.TapArguments{Backfill: walrus.TapNoBackfill}) if err != nil { return err } listener.tapFeed = tapFeed listener.counter = 1 listener.keyCounts = map[string]uint64{} listener.tapNotifier = sync.NewCond(&sync.Mutex{}) if trackDocs { listener.DocChannel = make(chan walrus.TapEvent, 100) } // Start a goroutine to broadcast to the tapNotifier whenever a channel or user/role changes: go func() { defer func() { listener.notifyStopping() if listener.DocChannel != nil { close(listener.DocChannel) } }() for event := range tapFeed.Events() { if event.Opcode == walrus.TapMutation || event.Opcode == walrus.TapDeletion { key := string(event.Key) if strings.HasPrefix(key, auth.UserKeyPrefix) || strings.HasPrefix(key, auth.RoleKeyPrefix) { if listener.OnDocChanged != nil { listener.OnDocChanged(key, event.Value) } listener.Notify(base.SetOf(key)) } else if trackDocs && !strings.HasPrefix(key, kSyncKeyPrefix) { if listener.OnDocChanged != nil { listener.OnDocChanged(key, event.Value) } listener.DocChannel <- event } } } }() return nil }
func TestLoaderFunction(t *testing.T) { var callsToLoader = 0 loader := func(id IDAndRev) (body Body, history Body, channels base.Set, err error) { callsToLoader++ if id.DocID[0] != 'J' { err = base.HTTPErrorf(404, "missing") } else { body = Body{ "_id": id.DocID, "_rev": id.RevID, } history = Body{"start": 1} channels = base.SetOf("*") } return } cache := NewRevisionCache(10, loader) body, history, channels, err := cache.Get("Jens", "1") assert.Equals(t, body["_id"], "Jens") assert.True(t, history != nil) assert.True(t, channels != nil) assert.Equals(t, err, error(nil)) assert.Equals(t, callsToLoader, 1) body, history, channels, err = cache.Get("Peter", "1") assert.DeepEquals(t, body, Body(nil)) assert.DeepEquals(t, err, base.HTTPErrorf(404, "missing")) assert.Equals(t, callsToLoader, 2) body, history, channels, err = cache.Get("Jens", "1") assert.Equals(t, body["_id"], "Jens") assert.True(t, history != nil) assert.True(t, channels != nil) assert.Equals(t, err, error(nil)) assert.Equals(t, callsToLoader, 2) body, history, channels, err = cache.Get("Peter", "1") assert.DeepEquals(t, body, Body(nil)) assert.DeepEquals(t, err, base.HTTPErrorf(404, "missing")) assert.Equals(t, callsToLoader, 3) }
// If the set contains "*", returns a set of only "*". Else returns the original set. func ExpandingStar(set base.Set) base.Set { if _, exists := set["*"]; exists { return base.SetOf("*") } return set }
func TestDocDeletionFromChannel(t *testing.T) { // See https://github.com/couchbase/couchbase-lite-ios/issues/59 // base.LogKeys["Changes"] = true // base.LogKeys["Cache"] = true rt := restTester{syncFn: `function(doc) {channel(doc.channel)}`} a := rt.ServerContext().Database("db").Authenticator() // Create user: alice, _ := a.NewUser("alice", "letmein", channels.SetOf("zero")) a.Save(alice) // Create a doc Alice can see: response := rt.send(request("PUT", "/db/alpha", `{"channel":"zero"}`)) // Check the _changes feed: rt.ServerContext().Database("db").WaitForPendingChanges() var changes struct { Results []db.ChangeEntry } response = rt.send(requestByUser("GET", "/db/_changes", "", "alice")) log.Printf("_changes looks like: %s", response.Body.Bytes()) json.Unmarshal(response.Body.Bytes(), &changes) assert.Equals(t, len(changes.Results), 1) since := changes.Results[0].Seq assert.Equals(t, since, db.SequenceID{Seq: 1}) assert.Equals(t, changes.Results[0].ID, "alpha") rev1 := changes.Results[0].Changes[0]["rev"] // Delete the document: assertStatus(t, rt.send(request("DELETE", "/db/alpha?rev="+rev1, "")), 200) // Get the updates from the _changes feed: time.Sleep(100 * time.Millisecond) response = rt.send(requestByUser("GET", fmt.Sprintf("/db/_changes?since=%s", since), "", "alice")) log.Printf("_changes looks like: %s", response.Body.Bytes()) changes.Results = nil json.Unmarshal(response.Body.Bytes(), &changes) assert.Equals(t, len(changes.Results), 1) assert.Equals(t, changes.Results[0].ID, "alpha") assert.Equals(t, changes.Results[0].Deleted, true) assert.DeepEquals(t, changes.Results[0].Removed, base.SetOf("zero")) rev2 := changes.Results[0].Changes[0]["rev"] // Now get the deleted revision: response = rt.send(requestByUser("GET", "/db/alpha?rev="+rev2, "", "alice")) assert.Equals(t, response.Code, 200) log.Printf("Deletion looks like: %s", response.Body.Bytes()) var docBody db.Body json.Unmarshal(response.Body.Bytes(), &docBody) assert.DeepEquals(t, docBody, db.Body{"_id": "alpha", "_rev": rev2, "_deleted": true}) // Access without deletion revID shouldn't be allowed (since doc is not in Alice's channels): response = rt.send(requestByUser("GET", "/db/alpha", "", "alice")) assert.Equals(t, response.Code, 403) // A bogus rev ID should return a 404: response = rt.send(requestByUser("GET", "/db/alpha?rev=bogus", "", "alice")) assert.Equals(t, response.Code, 404) // Get the old revision, which should still be accessible: response = rt.send(requestByUser("GET", "/db/alpha?rev="+rev1, "", "alice")) assert.Equals(t, response.Code, 200) }
// Returns a list of all the changes made on a channel, reading from a view instead of the // channel log. This will include all historical changes, but may omit very recent ones. func (db *Database) changesFeedFromView(channel string, options ChangesOptions, upToSeq uint64) (<-chan *ChangeEntry, error) { base.LogTo("Changes", "Getting 'changes' view for channel %q %#v", channel, options) since := options.Since[channel] endkey := []interface{}{channel, upToSeq} if upToSeq == 0 { endkey[1] = map[string]interface{}{} // infinity } totalLimit := options.Limit usingDocs := options.Conflicts || options.IncludeDocs opts := Body{"stale": false, "update_seq": true, "endkey": endkey, "include_docs": usingDocs} feed := make(chan *ChangeEntry, kChangesViewPageSize) // Generate the output in a new goroutine, writing to 'feed': go func() { defer close(feed) for { // Query the 'channels' view: opts["startkey"] = []interface{}{channel, since + 1} limit := totalLimit if limit == 0 || limit > kChangesViewPageSize { limit = kChangesViewPageSize } opts["limit"] = limit var waiter *changeWaiter if options.Wait { waiter = db.tapListener.NewWaiterWithChannels(base.SetOf(channel), nil) } var vres ViewResult var err error for len(vres.Rows) == 0 { base.LogTo("Changes+", "Querying 'changes' for channel %q %#v", channel, opts) vres = ViewResult{} err = db.Bucket.ViewCustom("sync_gateway", "channels", opts, &vres) if err != nil { base.Log("Error from 'channels' view: %v", err) return } if len(vres.Rows) == 0 { if waiter == nil || !waiter.Wait() { return } } } for _, row := range vres.Rows { key := row.Key.([]interface{}) since = uint64(key[1].(float64)) value := row.Value.([]interface{}) docID := value[0].(string) revID := value[1].(string) entry := &ChangeEntry{ seqNo: since, ID: docID, Changes: []ChangeRev{{"rev": revID}}, Deleted: (len(value) >= 3 && value[2].(bool)), } if len(value) >= 4 && value[3].(bool) { entry.Removed = channels.SetOf(channel) } else if usingDocs { doc, _ := unmarshalDocument(docID, row.Doc.Json) db.addDocToChangeEntry(doc, entry, options.IncludeDocs, options.Conflicts) } select { case <-options.Terminator: base.LogTo("Changes+", "Aborting changesFeedFromView") return case feed <- entry: } } // Step to the next page of results: nRows := len(vres.Rows) if nRows < kChangesViewPageSize || options.Wait { break } if totalLimit > 0 { totalLimit -= nRows if totalLimit <= 0 { break } } delete(opts, "stale") // we only need to update the index once } }() return feed, nil }
package db import ( "encoding/json" "fmt" "runtime" "sort" "strings" "testing" "github.com/couchbaselabs/go.assert" "github.com/couchbaselabs/sync_gateway/base" ) var testmap = RevTree{"3-three": {ID: "3-three", Parent: "2-two", Body: []byte("{}")}, "2-two": {ID: "2-two", Parent: "1-one", Channels: base.SetOf("ABC", "CBS")}, "1-one": {ID: "1-one", Channels: base.SetOf("ABC")}} var branchymap = RevTree{"3-three": {ID: "3-three", Parent: "2-two"}, "2-two": {ID: "2-two", Parent: "1-one"}, "1-one": {ID: "1-one"}, "3-drei": {ID: "3-drei", Parent: "2-two"}} const testJSON = `{"revs": ["3-three", "2-two", "1-one"], "parents": [1, 2, -1], "bodies": ["{}", "", ""], "channels": [null, ["ABC", "CBS"], ["ABC"]]}` func testUnmarshal(t *testing.T, jsonString string) RevTree { gotmap := RevTree{} assertNoError(t, json.Unmarshal([]byte(jsonString), &gotmap), "Couldn't parse RevTree from JSON") assert.DeepEquals(t, gotmap, testmap) return gotmap }