diff --git a/db/active_replicator_config.go b/db/active_replicator_config.go index 2cf61b50d6..a7f85c1943 100644 --- a/db/active_replicator_config.go +++ b/db/active_replicator_config.go @@ -160,6 +160,9 @@ func (arc ActiveReplicatorConfig) CheckpointHash(collectionIdx *int) (string, er if _, err := hash.Write([]byte(arc.RunAs)); err != nil { return "", err } + if arc.ActiveDB == nil || arc.ActiveDB.Bucket == nil { + return "", fmt.Errorf("error calculating checkpoint hash, cannot fetch bucket UUID") + } bucketUUID, err := arc.ActiveDB.Bucket.UUID() if err != nil { return "", err diff --git a/rest/replicatortest/replicator_test.go b/rest/replicatortest/replicator_test.go index a488a25272..c68a9210ab 100644 --- a/rest/replicatortest/replicator_test.go +++ b/rest/replicatortest/replicator_test.go @@ -8227,3 +8227,62 @@ func TestReplicatorWithCollectionsFailWithoutCollectionsEnabled(t *testing.T) { } } + +// TestPanicInCheckpointHash: +// - Create two rest testers +// - Add active replicator for rt1 to push to rt2 +// - Remove database context off os rt1 +// - Call start on active replicator, this would normally hit panic in ticket CBG-4070, should now error instead +func TestPanicInCheckpointHash(t *testing.T) { + if base.UnitTestUrlIsWalrus() { + t.Skip("Test requires Couchbase Server bucket - walrus panics on db removal underneath replication") + } + // Create two rest testers + rt1 := rest.NewRestTester(t, nil) + defer rt1.Close() + + rt2 := rest.NewRestTester(t, nil) + defer rt2.Close() + + username := "alice" + rt2.CreateUser(username, []string{username}) + + // construct remote URL to have _blipsync connect to + srv := httptest.NewServer(rt2.TestPublicHandler()) + defer srv.Close() + passiveDBURL, err := url.Parse(srv.URL + "/db") + require.NoError(t, err) + passiveDBURL.User = url.UserPassword(username, rest.RestTesterDefaultUserPassword) + + stats, err := base.SyncGatewayStats.NewDBStats(t.Name(), false, false, false, nil, nil) + require.NoError(t, err) + dbstats, err := stats.DBReplicatorStats(t.Name()) + require.NoError(t, err) + + ar, err := db.NewActiveReplicator(base.TestCtx(t), &db.ActiveReplicatorConfig{ + ID: t.Name(), + Direction: db.ActiveReplicatorTypePush, + ActiveDB: &db.Database{DatabaseContext: rt1.GetDatabase()}, + RemoteDBURL: passiveDBURL, + ReplicationStatsMap: dbstats, + Continuous: true, + CollectionsEnabled: !rt1.GetDatabase().OnlyDefaultCollection(), + }) + require.NoError(t, err) + + // remove the db context for rt1 off the server context + ok := rt1.ServerContext().RemoveDatabase(base.TestCtx(t), "db") + require.True(t, ok) + + // assert that the db context has been removed + require.EventuallyWithT(t, func(c *assert.CollectT) { + assert.Equal(c, 0, len(rt1.ServerContext().AllDatabases())) + }, time.Second*10, time.Millisecond*100) + + // attempt to start active replicator, this will hit panic in CBG-4070 pre this work due to the bucket being nil + // on the active db context + replicatorErr := ar.Start(base.TestCtx(t)) + assert.Error(t, replicatorErr) + assert.ErrorContains(t, replicatorErr, "cannot fetch bucket UUID") + +}