/** Test method for number to return determination logic. */
  @Test
  public void testNumberToReturnWhenNoBatchAndSmallishLimit() {
    final Random random = new Random(System.currentTimeMillis());

    final Document doc1 = BuilderFactory.start().addInteger("1", 0).build();
    final Document doc2 = BuilderFactory.start().addInteger("1", 1).build();
    final String databaseName = "db";
    final String collectionName = "collection";
    final Document query = doc1;
    final Document returnFields = doc2;
    final int numberToSkip = random.nextInt();
    final boolean tailable = random.nextBoolean();
    final ReadPreference readPreference =
        random.nextBoolean() ? ReadPreference.PRIMARY : ReadPreference.SECONDARY;
    final boolean noCursorTimeout = random.nextBoolean();
    final boolean awaitData = random.nextBoolean();
    final boolean exhaust = random.nextBoolean();
    final boolean partial = random.nextBoolean();

    final int batchSize = 0;
    final int limit = 5;

    final Query message =
        new Query(
            databaseName,
            collectionName,
            query,
            returnFields,
            batchSize,
            limit,
            numberToSkip,
            tailable,
            readPreference,
            noCursorTimeout,
            awaitData,
            exhaust,
            partial);

    assertEquals(databaseName, message.getDatabaseName());
    assertEquals(collectionName, message.getCollectionName());
    assertEquals(numberToSkip, message.getNumberToSkip());
    assertEquals(query, message.getQuery());
    assertEquals(returnFields, message.getReturnFields());
    assertEquals(Boolean.valueOf(awaitData), Boolean.valueOf(message.isAwaitData()));
    assertEquals(Boolean.valueOf(exhaust), Boolean.valueOf(message.isExhaust()));
    assertEquals(Boolean.valueOf(noCursorTimeout), Boolean.valueOf(message.isNoCursorTimeout()));
    assertEquals(Boolean.valueOf(partial), Boolean.valueOf(message.isPartial()));
    assertSame(readPreference, message.getReadPreference());
    assertEquals(Boolean.valueOf(tailable), Boolean.valueOf(message.isTailable()));

    assertEquals(batchSize, message.getBatchSize());
    assertEquals(limit, message.getLimit());
    assertEquals(5, message.getNumberToReturn());
  }
  /** Test method for {@link X509Authenticator#startAuthentication(Credential, Connection)} . */
  @Test
  public void testStartAuthentication() {
    final Credential credential = Credential.builder().userName("CN=foo").build();
    final Connection mockConnection = createMock(Connection.class);
    final Capture<ReplyCallback> capture = new Capture<ReplyCallback>();

    final DocumentBuilder b = BuilderFactory.start();
    b.add("authenticate", 1);
    b.add("user", "CN=foo");
    b.add("mechanism", X509Authenticator.MECHANISM);

    mockConnection.send(
        eq(new Command(X509Authenticator.EXTERNAL, Command.COMMAND_COLLECTION, b.build())),
        capture(capture));
    expectLastCall();

    replay(mockConnection);

    final X509Authenticator auth = new X509Authenticator();

    auth.startAuthentication(credential, mockConnection);

    verify(mockConnection);

    assertThat(capture.getValue(), instanceOf(X509ResponseCallback.class));
  }
  /** Test method for {@link Query#validateSize(int)} . */
  @Test
  public void testValidateSizeThrows() {
    final Random random = new Random(System.currentTimeMillis());

    final Document doc1 = BuilderFactory.start().addInteger("1", 0).build();
    final Document doc2 = BuilderFactory.start().addInteger("1", 1).build();
    final String databaseName = "db";
    final String collectionName = "collection";
    final Document query = doc1;
    final Document returnFields = doc2;
    final int batchSize = random.nextInt();
    final int limit = random.nextInt();
    final int numberToSkip = random.nextInt();
    final boolean tailable = random.nextBoolean();
    final ReadPreference readPreference =
        random.nextBoolean() ? ReadPreference.PRIMARY : ReadPreference.SECONDARY;
    final boolean noCursorTimeout = random.nextBoolean();
    final boolean awaitData = random.nextBoolean();
    final boolean exhaust = random.nextBoolean();
    final boolean partial = random.nextBoolean();

    final Query message =
        new Query(
            databaseName,
            collectionName,
            query,
            returnFields,
            batchSize,
            limit,
            numberToSkip,
            tailable,
            readPreference,
            noCursorTimeout,
            awaitData,
            exhaust,
            partial);

    try {
      message.validateSize(1);
    } catch (final DocumentToLargeException dtle) {
      assertEquals(1, dtle.getMaximumSize());
      assertEquals(24, dtle.getSize());
      assertSame(query, dtle.getDocument());
    }
  }
  /** Test method for {@link Query#validateSize(int)} . */
  @Test
  public void testValidateSize() {
    final Random random = new Random(System.currentTimeMillis());

    final Document doc1 = BuilderFactory.start().addInteger("1", 0).build();
    final Document doc2 = BuilderFactory.start().addInteger("1", 1).build();
    final String databaseName = "db";
    final String collectionName = "collection";
    final Document query = doc1;
    final Document returnFields = doc2;
    final int batchSize = random.nextInt();
    final int limit = random.nextInt();
    final int numberToSkip = random.nextInt();
    final boolean tailable = random.nextBoolean();
    final ReadPreference readPreference =
        random.nextBoolean() ? ReadPreference.PRIMARY : ReadPreference.SECONDARY;
    final boolean noCursorTimeout = random.nextBoolean();
    final boolean awaitData = random.nextBoolean();
    final boolean exhaust = random.nextBoolean();
    final boolean partial = random.nextBoolean();

    final Query message =
        new Query(
            databaseName,
            collectionName,
            query,
            returnFields,
            batchSize,
            limit,
            numberToSkip,
            tailable,
            readPreference,
            noCursorTimeout,
            awaitData,
            exhaust,
            partial);

    message.validateSize(1024);

    // Should be able to call again without visitor since size is cached.
    message.validateSize(1024);
  }
  /** Test method for {@link Query#equals(Object)}. */
  @SuppressWarnings("boxing")
  @Test
  public void testEqualsObject() {
    final Random random = new Random(System.currentTimeMillis());

    final List<Message> objs1 = new ArrayList<Message>();
    final List<Message> objs2 = new ArrayList<Message>();

    final Document doc1 = BuilderFactory.start().addInteger("1", 0).build();
    final Document doc2 = BuilderFactory.start().addInteger("1", 1).build();
    final Document doc3 = BuilderFactory.start().addInteger("1", 2).build();
    for (final String databaseName : Arrays.asList("n1", "n2", "n3")) {
      for (final String collectionName : Arrays.asList("c1", "c2", "c3")) {
        for (final Document query : Arrays.asList(doc1, doc2, doc3)) {
          for (final Document returnFields : Arrays.asList(null, doc1, doc2, doc3)) {
            for (final int batchSize : Arrays.asList(-1, 3, 8, 0xFF)) {
              for (final int limit : Arrays.asList(-1, 3, 8, 0xFF)) {
                for (final int numberToSkip : Arrays.asList(0, 1, 2, 0xFFF)) {

                  if (random.nextInt(10) == 1) {
                    final boolean tailable = random.nextBoolean();
                    final ReadPreference readPreference =
                        random.nextBoolean() ? ReadPreference.CLOSEST : ReadPreference.SECONDARY;
                    final boolean noCursorTimeout = random.nextBoolean();
                    final boolean awaitData = random.nextBoolean();
                    final boolean exhaust = random.nextBoolean();
                    final boolean partial = random.nextBoolean();

                    objs1.add(
                        new Query(
                            databaseName,
                            collectionName,
                            query,
                            returnFields,
                            batchSize,
                            limit,
                            numberToSkip,
                            tailable,
                            readPreference,
                            noCursorTimeout,
                            awaitData,
                            exhaust,
                            partial));
                    objs2.add(
                        new Query(
                            databaseName,
                            collectionName,
                            query,
                            returnFields,
                            batchSize,
                            limit,
                            numberToSkip,
                            tailable,
                            readPreference,
                            noCursorTimeout,
                            awaitData,
                            exhaust,
                            partial));
                  }
                }
              }
            }
          }
        }
      }
    }

    // Sanity check.
    assertEquals(objs1.size(), objs2.size());

    for (int i = 0; i < objs1.size(); ++i) {
      final Message obj1 = objs1.get(i);
      Message obj2 = objs2.get(i);

      assertTrue(obj1.equals(obj1));
      assertNotSame(obj1, obj2);
      assertEquals(obj1, obj2);

      assertEquals(obj1.hashCode(), obj2.hashCode());

      for (int j = i + 1; j < objs1.size(); ++j) {
        obj2 = objs2.get(j);

        assertFalse(obj1.equals(obj2));
        assertFalse(obj1.hashCode() == obj2.hashCode());
      }

      assertFalse(obj1.equals("foo"));
      assertFalse(obj1.equals(null));
      assertFalse(obj1.equals(new Command(obj1.getDatabaseName(), "coll", doc1)));
    }
  }
  /**
   * Test method for {@link Query#Query(Header,BsonInputStream)}.
   *
   * @throws IOException On a test failure.
   */
  @SuppressWarnings("boxing")
  @Test
  public void testQueryHeaderBsonInputStream() throws IOException {
    final Random random = new Random(System.currentTimeMillis());

    final List<Message> objs1 = new ArrayList<Message>();

    final Document doc1 = BuilderFactory.start().addInteger("1", 0).build();
    final Document doc2 = BuilderFactory.start().addInteger("1", 1).build();
    final Document doc3 = BuilderFactory.start().addInteger("1", 2).build();
    for (final String databaseName : Arrays.asList("n1", "n2", "n3")) {
      for (final String collectionName : Arrays.asList("c1", "c2", "c3")) {
        for (final Document query : Arrays.asList(doc1, doc2, doc3)) {
          for (final Document returnFields : Arrays.asList(doc1, doc2, doc3, null)) {
            for (final int batchSize : Arrays.asList(0)) {
              for (final int limit : Arrays.asList(0)) {
                for (final int numberToSkip : Arrays.asList(0, 1, 2, 0xFFF)) {
                  final boolean tailable = random.nextBoolean();
                  final ReadPreference readPreference =
                      random.nextBoolean() ? ReadPreference.PRIMARY : ReadPreference.SECONDARY;
                  final boolean noCursorTimeout = random.nextBoolean();
                  final boolean awaitData = random.nextBoolean();
                  final boolean exhaust = random.nextBoolean();
                  final boolean partial = random.nextBoolean();

                  objs1.add(
                      new Query(
                          databaseName,
                          collectionName,
                          query,
                          returnFields,
                          batchSize,
                          limit,
                          numberToSkip,
                          tailable,
                          readPreference,
                          noCursorTimeout,
                          awaitData,
                          exhaust,
                          partial));
                }
              }
            }
          }
        }
      }
    }

    for (final Message message : objs1) {

      final ByteArrayOutputStream out = new ByteArrayOutputStream();
      final BsonOutputStream bOut = new BsonOutputStream(out);

      message.write(1234, bOut);

      final byte[] bytes = out.toByteArray();
      assertThat(message.size(), is(bytes.length));

      final ByteArrayInputStream in = new ByteArrayInputStream(bytes);
      final BsonInputStream bIn = new BsonInputStream(in);

      final Header header = new Header(bIn);

      assertEquals(Operation.QUERY, header.getOperation());
      assertEquals(1234, header.getRequestId());
      assertEquals(0, header.getResponseId());
      assertEquals(out.size(), header.getLength());

      final Query read = new Query(header, bIn);
      if (message.getReadPreference() == ReadPreference.PRIMARY) {
        assertEquals(message, read);
      }
    }
  }