@@ -1580,7 +1580,7 @@ func TestActiveReplicatorInvalidCustomResolver(t *testing.T) {
15801580 _ , _ , _ , err = rt2Collection .PutExistingCurrentVersion (rt2Ctx , opts )
15811581 require .NoError (t , err )
15821582
1583- resolver := `function(conflict) {var mergedDoc = new Object();
1583+ resolver := `function(conflict) {var mergedDoc = new Object();
15841584 mergedDoc._cv = "@";
15851585 return mergedDoc;}` // invalid - setting cv to something that doesn't match either doc
15861586 customConflictResolver , err := db .NewCustomConflictResolver (ctx1 , resolver , rt1 .GetDatabase ().Options .JavascriptTimeout )
@@ -2276,6 +2276,187 @@ func TestActiveReplicatorConflictRemoveCVFromCache(t *testing.T) {
22762276 assert .Equal (t , rt2Version .CV .String (), docRev .HlvHistory )
22772277}
22782278
2279+ func TestActiveReplicatorV4DefaultResolverWithTombstoneLocal (t * testing.T ) {
2280+ base .RequireNumTestBuckets (t , 2 )
2281+
2282+ // Passive
2283+ passiveRT := rest .NewRestTester (t , & rest.RestTesterConfig {
2284+ SyncFn : channels .DocChannelsSyncFunction ,
2285+ DatabaseConfig : & rest.DatabaseConfig {
2286+ DbConfig : rest.DbConfig {
2287+ Name : "passivedb" ,
2288+ },
2289+ },
2290+ })
2291+ defer passiveRT .Close ()
2292+ username := "alice"
2293+ passiveRT .CreateUser (username , []string {username })
2294+
2295+ // Active
2296+ activeRT := rest .NewRestTester (t , & rest.RestTesterConfig {
2297+ SyncFn : channels .DocChannelsSyncFunction ,
2298+ DatabaseConfig : & rest.DatabaseConfig {
2299+ DbConfig : rest.DbConfig {
2300+ Name : "activedb" ,
2301+ },
2302+ },
2303+ })
2304+ defer activeRT .Close ()
2305+ ctx1 := activeRT .Context ()
2306+
2307+ docID := rest .SafeDocumentName (t , t .Name ())
2308+ rt1Version := activeRT .PutDoc (docID , `{"source":"activeRT","channels":["alice"]}` )
2309+ // delete local version
2310+ rt1Version = activeRT .DeleteDoc (docID , rt1Version )
2311+ activeRT .WaitForPendingChanges ()
2312+ // create conflicting update on passiveRT
2313+ rt2Version := passiveRT .PutDoc (docID , `{"source":"passiveRT","channels":["alice"]}` )
2314+ rt2Version = passiveRT .UpdateDoc (docID , rt2Version , `{"source":"passiveRT-updated","channels":["alice"]}` )
2315+ passiveRT .WaitForPendingChanges ()
2316+
2317+ replicationID := rest .SafeDocumentName (t , t .Name ())
2318+ resolverFunc , err := db .NewConflictResolverFuncForHLV (ctx1 , db .ConflictResolverDefault , "" , activeRT .GetDatabase ().Options .JavascriptTimeout )
2319+ require .NoError (t , err )
2320+
2321+ stats , err := base .SyncGatewayStats .NewDBStats (rest .SafeDocumentName (t , t .Name ()), false , false , false , nil , nil )
2322+ require .NoError (t , err )
2323+ replicationStats , err := stats .DBReplicatorStats (replicationID )
2324+ require .NoError (t , err )
2325+
2326+ ar , err := db .NewActiveReplicator (ctx1 , & db.ActiveReplicatorConfig {
2327+ ID : replicationID ,
2328+ Direction : db .ActiveReplicatorTypePushAndPull ,
2329+ RemoteDBURL : userDBURL (passiveRT , username ),
2330+ ActiveDB : & db.Database {
2331+ DatabaseContext : activeRT .GetDatabase (),
2332+ },
2333+ ChangesBatchSize : 200 ,
2334+ ReplicationStatsMap : replicationStats ,
2335+ ConflictResolverFuncForHLV : resolverFunc ,
2336+ CollectionsEnabled : ! activeRT .GetDatabase ().OnlyDefaultCollection (),
2337+ Continuous : true ,
2338+ })
2339+ require .NoError (t , err )
2340+ defer func () { assert .NoError (t , ar .Stop ()) }()
2341+
2342+ require .NoError (t , ar .Start (ctx1 ))
2343+
2344+ require .EventuallyWithT (t , func (c * assert.CollectT ) {
2345+ assert .Equal (c , int64 (1 ), replicationStats .ConflictResolvedLocalCount .Value ())
2346+ }, 10 * time .Second , 100 * time .Millisecond )
2347+
2348+ docBodyBytes := []byte (`{}` )
2349+ newRev := getRevTreeID (t , rt2Version .RevTreeID , docBodyBytes )
2350+ conflictResVersion := rt1Version
2351+ conflictResVersion .RevTreeID = newRev
2352+
2353+ // expect local doc to win and still be tombstone with remote docs revision history
2354+ activeRT .WaitForTombstone (docID , conflictResVersion )
2355+
2356+ rt1Doc := activeRT .GetDocument (docID )
2357+ // assert that remote cv is in pv now
2358+ assert .Equal (t , rt2Version .CV .Value , rt1Doc .HLV .PreviousVersions [rt2Version .CV .SourceID ])
2359+ // assert doc is still a tombstone
2360+ assert .True (t , rt1Doc .IsDeleted ())
2361+ // assert on rev tree structure
2362+ docHistoryLeaves := rt1Doc .History .GetLeaves ()
2363+ require .Len (t , docHistoryLeaves , 2 )
2364+ for _ , revID := range docHistoryLeaves {
2365+ assert .True (t , rt1Doc .History [revID ].Deleted )
2366+ }
2367+
2368+ }
2369+
2370+ func TestActiveReplicatorV4DefaultResolverWithTombstoneRemote (t * testing.T ) {
2371+ base .RequireNumTestBuckets (t , 2 )
2372+
2373+ // Passive
2374+ passiveRT := rest .NewRestTester (t , & rest.RestTesterConfig {
2375+ SyncFn : channels .DocChannelsSyncFunction ,
2376+ DatabaseConfig : & rest.DatabaseConfig {
2377+ DbConfig : rest.DbConfig {
2378+ Name : "passivedb" ,
2379+ },
2380+ },
2381+ })
2382+ defer passiveRT .Close ()
2383+ username := "alice"
2384+ passiveRT .CreateUser (username , []string {username })
2385+
2386+ // Active
2387+ activeRT := rest .NewRestTester (t , & rest.RestTesterConfig {
2388+ SyncFn : channels .DocChannelsSyncFunction ,
2389+ DatabaseConfig : & rest.DatabaseConfig {
2390+ DbConfig : rest.DbConfig {
2391+ Name : "activedb" ,
2392+ },
2393+ },
2394+ })
2395+ defer activeRT .Close ()
2396+ ctx1 := activeRT .Context ()
2397+ docID := rest .SafeDocumentName (t , t .Name ())
2398+
2399+ rt2Version := passiveRT .PutDoc (docID , `{"source":"passiveRT","channels":["alice"]}` )
2400+ rt2Version = passiveRT .DeleteDoc (docID , rt2Version )
2401+ passiveRT .WaitForPendingChanges ()
2402+ // create conflicting update on activeRT
2403+ rt1Version := activeRT .PutDoc (docID , `{"source":"activeRT","channels":["alice"]}` )
2404+ // delete local version
2405+ rt1Version = activeRT .UpdateDoc (docID , rt1Version , `{"source":"activeRT-updated","channels":["alice"]}` )
2406+ activeRT .WaitForPendingChanges ()
2407+
2408+ replicationID := rest .SafeDocumentName (t , t .Name ())
2409+ resolverFunc , err := db .NewConflictResolverFuncForHLV (ctx1 , db .ConflictResolverDefault , "" , activeRT .GetDatabase ().Options .JavascriptTimeout )
2410+ require .NoError (t , err )
2411+
2412+ stats , err := base .SyncGatewayStats .NewDBStats (rest .SafeDocumentName (t , t .Name ()), false , false , false , nil , nil )
2413+ require .NoError (t , err )
2414+ replicationStats , err := stats .DBReplicatorStats (replicationID )
2415+ require .NoError (t , err )
2416+
2417+ ar , err := db .NewActiveReplicator (ctx1 , & db.ActiveReplicatorConfig {
2418+ ID : replicationID ,
2419+ Direction : db .ActiveReplicatorTypePushAndPull ,
2420+ RemoteDBURL : userDBURL (passiveRT , username ),
2421+ ActiveDB : & db.Database {
2422+ DatabaseContext : activeRT .GetDatabase (),
2423+ },
2424+ ChangesBatchSize : 200 ,
2425+ ReplicationStatsMap : replicationStats ,
2426+ ConflictResolverFuncForHLV : resolverFunc ,
2427+ CollectionsEnabled : ! activeRT .GetDatabase ().OnlyDefaultCollection (),
2428+ Continuous : true ,
2429+ })
2430+ require .NoError (t , err )
2431+ defer func () { assert .NoError (t , ar .Stop ()) }()
2432+
2433+ require .NoError (t , ar .Start (ctx1 ))
2434+
2435+ require .EventuallyWithT (t , func (c * assert.CollectT ) {
2436+ assert .Equal (c , int64 (1 ), replicationStats .ConflictResolvedRemoteCount .Value ())
2437+ }, 10 * time .Second , 100 * time .Millisecond )
2438+
2439+ // expect remote doc to win but local doc ends up with longer history and given both local and remote branches
2440+ // are tombstoned then we end up moving local rev 3-xyz to be the current rev
2441+ newRev := getRevTreeID (t , rt1Version .RevTreeID , []byte (db .DeletedDocument ))
2442+ conflictResVersion := rt2Version
2443+ conflictResVersion .RevTreeID = newRev
2444+ activeRT .WaitForTombstone (docID , conflictResVersion )
2445+
2446+ rt1Doc := activeRT .GetDocument (docID )
2447+ // assert that remote cv is in pv now
2448+ assert .Equal (t , rt1Version .CV .Value , rt1Doc .HLV .PreviousVersions [rt1Version .CV .SourceID ])
2449+ // assert doc is still a tombstone
2450+ assert .True (t , rt1Doc .IsDeleted ())
2451+ // assert on rev tree structure
2452+ docHistoryLeaves := rt1Doc .History .GetLeaves ()
2453+ require .Len (t , docHistoryLeaves , 2 )
2454+ for _ , revID := range docHistoryLeaves {
2455+ assert .True (t , rt1Doc .History [revID ].Deleted )
2456+ }
2457+
2458+ }
2459+
22792460// getRevTreeID create a revtree ID for a new revision that is a child of the parentRevID for a given body.
22802461func getRevTreeID (t * testing.T , parentRevID string , body []byte ) string {
22812462 prevGeneration , _ := db .ParseRevID (t .Context (), parentRevID )
0 commit comments