// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "android_webview/native/aw_contents_client_bridge.h"

#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/android/scoped_java_ref.h"
#include "base/bind.h"
#include "base/memory/scoped_ptr.h"
#include "base/run_loop.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "jni/MockAwContentsClientBridge_jni.h"
#include "net/android/net_jni_registrar.h"
#include "net/ssl/ssl_cert_request_info.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"


using base::android::AttachCurrentThread;
using base::android::ScopedJavaLocalRef;
using net::SSLCertRequestInfo;
using net::SSLClientCertType;
using net::X509Certificate;
using testing::NotNull;
using testing::Test;

namespace android_webview {

namespace {

// Tests the android_webview contents client bridge.
class AwContentsClientBridgeTest : public Test {
 public:
  typedef AwContentsClientBridge::SelectCertificateCallback
      SelectCertificateCallback;

  AwContentsClientBridgeTest() { }

  // Callback method called when a cert is selected.
  void CertSelected(X509Certificate* cert);
 protected:
  virtual void SetUp();
  void TestCertType(SSLClientCertType type, const std::string& expected_name);
  // Create the TestBrowserThreads. Just instantiate the member variable.
  content::TestBrowserThreadBundle thread_bundle_;
  base::android::ScopedJavaGlobalRef<jobject> jbridge_;
  scoped_ptr<AwContentsClientBridge> bridge_;
  scoped_refptr<SSLCertRequestInfo> cert_request_info_;
  X509Certificate* selected_cert_;
  int cert_selected_callbacks_;
  JNIEnv* env_;
};

}   // namespace

void AwContentsClientBridgeTest::SetUp() {
  env_ = AttachCurrentThread();
  ASSERT_THAT(env_, NotNull());
  ASSERT_TRUE(android_webview::RegisterAwContentsClientBridge(env_));
  ASSERT_TRUE(RegisterNativesImpl(env_));
  ASSERT_TRUE(net::android::RegisterJni(env_));
  jbridge_.Reset(env_,
      Java_MockAwContentsClientBridge_getAwContentsClientBridge(env_).obj());
  bridge_.reset(new AwContentsClientBridge(env_, jbridge_.obj()));
  selected_cert_ = NULL;
  cert_selected_callbacks_ = 0;
  cert_request_info_ = new net::SSLCertRequestInfo;
}

void AwContentsClientBridgeTest::CertSelected(X509Certificate* cert) {
  selected_cert_ = cert;
  cert_selected_callbacks_++;
}

TEST_F(AwContentsClientBridgeTest, TestClientCertKeyTypesCorrectlyEncoded) {
  SSLClientCertType cert_types[3] = {net::CLIENT_CERT_RSA_SIGN,
      net::CLIENT_CERT_DSS_SIGN, net::CLIENT_CERT_ECDSA_SIGN};
  std::string expected_names[3] = {"RSA", "DSA" ,"ECDSA"};

  for(int i = 0; i < 3; i++) {
    TestCertType(cert_types[i], expected_names[i]);
  }
}

void AwContentsClientBridgeTest::TestCertType(SSLClientCertType type,
      const std::string& expected_name) {
  cert_request_info_->cert_key_types.clear();
  cert_request_info_->cert_key_types.push_back(type);
  bridge_->SelectClientCertificate(
      cert_request_info_.get(),
      base::Bind(
          &AwContentsClientBridgeTest::CertSelected,
          base::Unretained(static_cast<AwContentsClientBridgeTest*>(this))));
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(0, cert_selected_callbacks_);
  ScopedJavaLocalRef<jobjectArray> key_types =
      Java_MockAwContentsClientBridge_getKeyTypes(env_, jbridge_.obj());
  std::vector<std::string> vec;
  base::android::AppendJavaStringArrayToStringVector(env_,
                                                     key_types.obj(),
                                                     &vec);
  EXPECT_EQ(1u, vec.size());
  EXPECT_EQ(expected_name, vec[0]);
}

// Verify that ProvideClientCertificateResponse works properly when the client
// responds with a null key.
TEST_F(AwContentsClientBridgeTest,
    TestProvideClientCertificateResponseCallsCallbackOnNullKey) {
  // Call SelectClientCertificate to create a callback id that mock java object
  // can call on.
  bridge_->SelectClientCertificate(
    cert_request_info_.get(),
    base::Bind(
        &AwContentsClientBridgeTest::CertSelected,
        base::Unretained(static_cast<AwContentsClientBridgeTest*>(this))));
  bridge_->ProvideClientCertificateResponse(env_, jbridge_.obj(),
      Java_MockAwContentsClientBridge_getRequestId(env_, jbridge_.obj()),
      Java_MockAwContentsClientBridge_createTestCertChain(
          env_, jbridge_.obj()).obj(),
      NULL);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(NULL, selected_cert_);
  EXPECT_EQ(1, cert_selected_callbacks_);
}

// Verify that ProvideClientCertificateResponse calls the callback with
// NULL parameters when private key is not provided.
TEST_F(AwContentsClientBridgeTest,
    TestProvideClientCertificateResponseCallsCallbackOnNullChain) {
  // Call SelectClientCertificate to create a callback id that mock java object
  // can call on.
  bridge_->SelectClientCertificate(
    cert_request_info_.get(),
    base::Bind(
        &AwContentsClientBridgeTest::CertSelected,
        base::Unretained(static_cast<AwContentsClientBridgeTest*>(this))));
  int requestId =
    Java_MockAwContentsClientBridge_getRequestId(env_, jbridge_.obj());
  bridge_->ProvideClientCertificateResponse(env_, jbridge_.obj(),
      requestId,
      NULL,
      Java_MockAwContentsClientBridge_createTestPrivateKey(
          env_, jbridge_.obj()).obj());
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(NULL, selected_cert_);
  EXPECT_EQ(1, cert_selected_callbacks_);
}

}   // android_webview