@@ -368,3 +368,107 @@ function sync(doc, oldDoc){
368368 require .NoError (t , err )
369369 assert .Equal (t , string (bodyGet ), string (specBody ))
370370}
371+
372+ // TestResyncRequireResyncDefaultMetadataID
373+ //
374+ // Setup: db1 owns _default.sg_test_0 (gets non-default metadataID). db2 owns _default._default (default metadata id).
375+ // Delete db1, update db2 to include _default.sg_test_0. Because db2 includes _default._default,
376+ // it gets metadataID="_default". Assert that after resync regenerate sequences, db2 comes online successfully.
377+ func TestResyncRequireResyncDefaultMetadataID (t * testing.T ) {
378+ if base .UnitTestUrlIsWalrus () {
379+ t .Skip ("Test requires Couchbase Server" )
380+ }
381+ base .TestRequiresCollections (t )
382+ base .SetUpTestLogging (t , base .LevelInfo , base .KeyAll )
383+
384+ ctx := base .TestCtx (t )
385+ tb := base .GetTestBucket (t )
386+ defer tb .Close (ctx )
387+
388+ const namedCollectionName = "sg_test_0"
389+ require .NoError (t , tb .CreateDataStore (ctx , base.ScopeAndCollectionName {Scope : base .DefaultScope , Collection : namedCollectionName }))
390+ defer func () {
391+ assert .NoError (t , tb .DropDataStore (base.ScopeAndCollectionName {Scope : base .DefaultScope , Collection : namedCollectionName }))
392+ }()
393+
394+ rt := rest .NewRestTester (t , & rest.RestTesterConfig {
395+ CustomTestBucket : tb .NoCloseClone (),
396+ PersistentConfig : true ,
397+ })
398+ defer rt .Close ()
399+
400+ db1Name := "db1"
401+ db2Name := "db2"
402+
403+ // Create db1 with _default scope and sg_test_0 collection only — gets metadataID="db1"
404+ db1Cfg := rt .NewDbConfig ()
405+ db1Cfg .Scopes = rest.ScopesConfig {
406+ base .DefaultScope : rest.ScopeConfig {
407+ Collections : rest.CollectionsConfig {
408+ namedCollectionName : {},
409+ },
410+ },
411+ }
412+ rest .RequireStatus (t , rt .CreateDatabase (db1Name , db1Cfg ), http .StatusCreated )
413+
414+ // Write a doc to the sg_test_0 collection in db1
415+ keyspace := db1Name + "._default." + namedCollectionName
416+ resp := rt .SendAdminRequest ("PUT" , "/" + keyspace + "/testDoc1" , `{"foo":"bar"}` )
417+ rest .RequireStatus (t , resp , http .StatusCreated )
418+
419+ // Create db2 with _default scope and _default collection only — gets metadataID="_default"
420+ db2Cfg := rt .NewDbConfig ()
421+ db2Cfg .Scopes = rest.ScopesConfig {
422+ base .DefaultScope : rest.ScopeConfig {
423+ Collections : rest.CollectionsConfig {
424+ base .DefaultCollection : {},
425+ },
426+ },
427+ }
428+ rest .RequireStatus (t , rt .CreateDatabase (db2Name , db2Cfg ), http .StatusCreated )
429+
430+ // Delete db1 to free the sg_test_0 collection
431+ resp = rt .SendAdminRequest ("DELETE" , "/" + db1Name + "/" , "" )
432+ rest .RequireStatus (t , resp , http .StatusOK )
433+
434+ // Update db2 to include both _default._default AND _default.sg_test_0.
435+ // db2 keeps metadataID="_default" because it includes _default._default.
436+ // The sg_test_0 collection's syncInfo still has db1's non-default metadataID — requires resync.
437+ db2Cfg .Scopes = rest.ScopesConfig {
438+ base .DefaultScope : rest.ScopeConfig {
439+ Collections : rest.CollectionsConfig {
440+ base .DefaultCollection : {},
441+ namedCollectionName : {},
442+ },
443+ },
444+ }
445+ rest .RequireStatus (t , rt .UpsertDbConfig (db2Name , db2Cfg ), http .StatusCreated )
446+
447+ // db2 should now require resync for the sg_test_0 collection and be offline
448+ rt .WaitForDatabaseState (db2Name , db .RunStateString [db .DBOffline ])
449+
450+ dbRoot := rt .GetDatabaseRoot (db2Name )
451+ expectedResyncCollection := base .DefaultScope + "." + namedCollectionName
452+ require .Contains (t , dbRoot .RequireResync , expectedResyncCollection ,
453+ "sg_test_0 collection should require resync after moving from db1 to db2" )
454+
455+ // Run resync with regenerate_sequences=true on the sg_test_0 collection only
456+ resyncBody := fmt .Sprintf (`{"scopes": {"_default": ["%s"]}}` , namedCollectionName )
457+ resp = rt .SendAdminRequest ("POST" , "/" + db2Name + "/_resync?action=start®enerate_sequences=true" , resyncBody )
458+ rest .RequireStatus (t , resp , http .StatusOK )
459+
460+ // Wait for resync to complete
461+ rt .WaitForResyncDCPStatusForDB (db .BackgroundProcessStateCompleted , db2Name )
462+
463+ // Verify in-memory RequireResync is cleared — this passes because Run() updated db.RequireResync
464+ dbRoot = rt .GetDatabaseRoot (db2Name )
465+ assert .Empty (t , dbRoot .RequireResync , "RequireResync should be empty after resync completed" )
466+
467+ // Bring db2 online via POST /_config with offline:false.
468+ // TakeDbOnline calls ReloadDatabase which re-runs InitSyncInfo for every collection.
469+ // Requires resync should evaluate to false so syncInfo should be updated with the new metadataID and the database should come online successfully.
470+ resp = rt .SendAdminRequest ("POST" , "/" + db2Name + "/_config" , `{"offline":false}` )
471+ rest .RequireStatus (t , resp , http .StatusCreated )
472+
473+ rt .WaitForDatabaseState (db2Name , db .RunStateString [db .DBOnline ])
474+ }
0 commit comments