Skip to content

Conversation

@jayant-dhingra-cb
Copy link

No description provided.

@gemini-code-assist
Copy link

Summary of Changes

Hello @jayant-dhingra-cb, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly expands the end-to-end test coverage for the Replicator API by introducing a new test suite specifically designed for the CollectionConfiguration pattern. The added tests validate the API's behavior across numerous replication scenarios, ensuring its stability, correctness, and adherence to expected functionality, including advanced features like filtering, conflict resolution, and configuration management.

Highlights

  • New Replicator API Test Suite: Introduces ReplicatorNewApiTests with 25 new end-to-end tests for the updated Replicator API, which utilizes CollectionConfiguration objects.
  • Comprehensive Replication Scenarios: The new tests cover a wide range of replication functionalities, including default configuration values, status and document change listeners, empty push operations, and various filtering mechanisms (pull, push, nested objects, continuous).
  • Advanced Replication Behavior Validation: Specific tests validate checkpoint handling (with and without reset, continuous), document conflict resolution during pull, document deletion events, the "push and forget" pattern, and the performance of filtered replications.
  • API Robustness Checks: Includes tests to ensure the proper removal of change listeners, the immutability of ReplicatorConfiguration objects after creation, and the correct functioning of all configuration setter methods.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a comprehensive test suite for the new replicator API, which is a great addition. The tests cover a wide range of scenarios, from basic configuration to complex conflict and filtering cases. My review focuses on improving the robustness of a test helper function, increasing type safety, and removing some dead code. Overall, this is a solid set of tests.

Comment on lines +89 to +119
let listenerToken: string;
const completionPromise = new Promise<void>((resolve, reject) => {
replicator
.addChangeListener((change) => {
const status = change.status;
const activityLevel = status.getActivityLevel();

if (
config.getContinuous() &&
activityLevel == ReplicatorActivityLevel.IDLE
) {
if (
status.getProgress().getCompleted() ==
status.getProgress().getTotal()
)
replicator.stop();
}

if (activityLevel === ReplicatorActivityLevel.STOPPED) {
const error = status.getError();
if (error) {
reject(new Error(`Replication ${JSON.stringify(error)}`));
} else {
resolve();
}
}
})
.then((token) => {
listenerToken = token;
});
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

There are a couple of issues here that could lead to hanging tests or crashes. First, listenerToken is not initialized and could be undefined if addChangeListener fails. It should be typed as string | undefined for safety. Second, if the promise from addChangeListener rejects, it's an unhandled rejection, and completionPromise will hang, causing the test to time out. You should add a .catch(reject) to the promise chain to propagate the error.

    let listenerToken: string | undefined;
    const completionPromise = new Promise<void>((resolve, reject) => {
      replicator
        .addChangeListener((change) => {
          const status = change.status;
          const activityLevel = status.getActivityLevel();

          if (
            config.getContinuous() &&
            activityLevel == ReplicatorActivityLevel.IDLE
          ) {
            if (
              status.getProgress().getCompleted() ==
              status.getProgress().getTotal()
            )
              replicator.stop();
          }

          if (activityLevel === ReplicatorActivityLevel.STOPPED) {
            const error = status.getError();
            if (error) {
              reject(new Error(`Replication ${JSON.stringify(error)}`));
            } else {
              resolve();
            }
          }
        })
        .then((token) => {
          listenerToken = token;
        })
        .catch(reject);
    });

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know enough about Promises to know if either this would be an actual issue only inside the test or if this is standard practice and we should accommodate such use ?better?. In theory we don't expect users to remove a listener right after they create it as the expectation is that they are looking to receive at least an update before they would dispose it and so the above is redundant.

Comment on lines +127 to +128
await replicator.removeChangeListener(listenerToken);
replicator.stop();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Since listenerToken can be undefined (as it's set asynchronously and the operation might fail), you should add a guard to prevent calling removeChangeListener with an invalid token. This will prevent a crash in the finally block.

      if (listenerToken) {
        await replicator.removeChangeListener(listenerToken);
      }
      replicator.stop();

Comment on lines +672 to +679
if (!(otherDb instanceof Database)) {
return {
testName: 'testDocumentReplicationEventWithPullConflict',
success: false,
message: "otherDb isn't a database instance",
data: undefined,
};
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This if (!(otherDb instanceof Database)) check is unreachable. The new Database(...) constructor on line 669 will either return a Database instance or throw an error. In the success case, otherDb is guaranteed to be an instance of Database, so this condition will never be true. This block can be safely removed.

Copy link

@velicuvlad velicuvlad Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious about this check? as pointed out by the bot, I expect that new Database... call above to throw at a low level iOS/LiteCore in case the database cannot be opened. Including creating a new one if it doesn't already exists in "open". Is that not the case for RN?

We should never end up into this if basically.

Comment on lines +811 to +817
const replicatedDocuments: any = [];

const token = await replicator.addDocumentChangeListener((change: any) => {
change.documents.forEach((document: any) =>
replicatedDocuments.push(document)
);
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using any for replicatedDocuments, change, and document reduces type safety. You should use the specific types from cblite-js like ReplicatedDocument and DocumentReplication. You'll need to import these types to use them in the suggestion.

      const replicatedDocuments: ReplicatedDocument[] = [];

      const token = await replicator.addDocumentChangeListener((change: DocumentReplication) => {
        change.documents.forEach((document) =>
          replicatedDocuments.push(document)
        );
      });

if (!this.otherDatabase) {
const databaseResult = await this.getDatabase(
this.otherDatabaseName,
this.otherDatabaseName,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extra space

} catch (e) {
console.error(e);
} finally {
await replicator.removeChangeListener(listenerToken);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally we would want to have listenerToken.remove() here as discussed in zoom.

} catch (e) {
console.error(e);
} finally {
await replicator.removeChangeListener(listenerToken);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally we would want to have listenerToken.remove() here as discussed in zoom.

await this.sleep(500);

// Clean up
await replicator.removeChangeListener(token);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

await this.sleep(500);

// Clean up
await replicator.removeChangeListener(token);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here


await replicator.start(false);
await replicatorCompletionPromise;
await replicator.removeChangeListener(listenerToken);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here etc.

Comment on lines +89 to +119
let listenerToken: string;
const completionPromise = new Promise<void>((resolve, reject) => {
replicator
.addChangeListener((change) => {
const status = change.status;
const activityLevel = status.getActivityLevel();

if (
config.getContinuous() &&
activityLevel == ReplicatorActivityLevel.IDLE
) {
if (
status.getProgress().getCompleted() ==
status.getProgress().getTotal()
)
replicator.stop();
}

if (activityLevel === ReplicatorActivityLevel.STOPPED) {
const error = status.getError();
if (error) {
reject(new Error(`Replication ${JSON.stringify(error)}`));
} else {
resolve();
}
}
})
.then((token) => {
listenerToken = token;
});
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know enough about Promises to know if either this would be an actual issue only inside the test or if this is standard practice and we should accommodate such use ?better?. In theory we don't expect users to remove a listener right after they create it as the expectation is that they are looking to receive at least an update before they would dispose it and so the above is redundant.

Comment on lines +672 to +679
if (!(otherDb instanceof Database)) {
return {
testName: 'testDocumentReplicationEventWithPullConflict',
success: false,
message: "otherDb isn't a database instance",
data: undefined,
};
}
Copy link

@velicuvlad velicuvlad Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious about this check? as pointed out by the bot, I expect that new Database... call above to throw at a low level iOS/LiteCore in case the database cannot be opened. Including creating a new one if it doesn't already exists in "open". Is that not the case for RN?

We should never end up into this if basically.

@velicuvlad
Copy link

If time permits, we should list/inventory this tests somewhere in a readme or similar. See https://github.com/couchbaselabs/couchbase-lite-api/tree/master/spec/tests for examples

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants