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

#import "base/strings/sys_string_conversions.h"
#import "base/strings/utf_string_conversions.h"
#import "base/test/ios/wait_util.h"
#import "ios/web/public/test/fakes/fake_web_state_delegate.h"
#import "ios/web/public/test/js_test_util.h"
#import "ios/web/public/test/navigation_test_util.h"
#import "ios/web/public/test/web_test_with_web_state.h"
#import "net/test/embedded_test_server/default_handlers.h"
#import "net/test/embedded_test_server/embedded_test_server.h"
#import "testing/gtest_mac.h"
#import "url/gurl.h"

using base::test::ios::kWaitForPageLoadTimeout;
using base::test::ios::WaitUntilConditionOrTimeout;

namespace web {

// Test fixture for WebStateDelegate::OnAuthRequired integration tests.
class HttpAuthTest : public WebTestWithWebState {
 protected:
  void SetUp() override {
    WebTestWithWebState::SetUp();
    web_state()->SetDelegate(&delegate_);
    RegisterDefaultHandlers(&server_);
    ASSERT_TRUE(server_.Start());
  }
  // Waits until WebStateDelegate::OnAuthRequired callback is called.
  [[nodiscard]] bool WaitForOnAuthRequiredCallback() {
    delegate_.ClearLastAuthenticationRequest();
    return WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^bool {
      return delegate_.last_authentication_request();
    });
  }
  net::EmbeddedTestServer server_;
  FakeWebStateDelegate delegate_;
};

// Tests successful basic authentication.
TEST_F(HttpAuthTest, SuccessfullBasicAuth) {
  // Load the page which requests basic HTTP authentication.
  GURL url = server_.GetURL("/auth-basic?password=goodpass&realm=Realm1");
  test::LoadUrl(web_state(), url);
  ASSERT_TRUE(WaitForOnAuthRequiredCallback());

  // Verify that callback receives correct WebState.
  auto* auth_request = delegate_.last_authentication_request();
  EXPECT_EQ(web_state(), auth_request->web_state);

  // Verify that callback receives correctly configured protection space.
  NSURLProtectionSpace* protection_space = auth_request->protection_space;
  EXPECT_NSEQ(@"Realm1", protection_space.realm);
  EXPECT_FALSE(protection_space.receivesCredentialSecurely);
  EXPECT_FALSE([protection_space isProxy]);
  EXPECT_EQ(url.GetHost(), base::SysNSStringToUTF8(protection_space.host));
  EXPECT_EQ(server_.port(),
            base::checked_cast<uint16_t>(protection_space.port));
  EXPECT_FALSE(protection_space.proxyType);
  EXPECT_NSEQ(NSURLProtectionSpaceHTTP, protection_space.protocol);
  EXPECT_NSEQ(NSURLAuthenticationMethodHTTPBasic,
              protection_space.authenticationMethod);

  // Make sure that authenticated page renders expected text.
  ASSERT_TRUE(web_state()->IsLoading());
  auth_request = delegate_.last_authentication_request();
  ASSERT_TRUE(auth_request);
  std::move(auth_request->http_auth_callback).Run(@"me", @"goodpass");
  ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{
    return web_state()->GetTitle() == u"me/goodpass";
  }));
}

// Tests unsuccessful basic authentication.
TEST_F(HttpAuthTest, UnsucessfulBasicAuth) {
  // Load the page which requests basic HTTP authentication.
  GURL url = server_.GetURL("/auth-basic?password=goodpass&realm=Realm2");
  test::LoadUrl(web_state(), url);
  ASSERT_TRUE(WaitForOnAuthRequiredCallback());

  // Make sure that incorrect credentials request authentication again.
  auto* auth_request = delegate_.last_authentication_request();
  std::move(auth_request->http_auth_callback).Run(@"me", @"badpass");
  ASSERT_TRUE(WaitForOnAuthRequiredCallback());

  // Verify that callback receives correct WebState.
  auth_request = delegate_.last_authentication_request();
  EXPECT_EQ(web_state(), auth_request->web_state);

  // Verify that callback receives correctly configured protection space.
  NSURLProtectionSpace* protection_space = auth_request->protection_space;
  EXPECT_NSEQ(@"Realm2", protection_space.realm);
  EXPECT_FALSE(protection_space.receivesCredentialSecurely);
  EXPECT_FALSE([protection_space isProxy]);
  EXPECT_EQ(url.GetHost(), base::SysNSStringToUTF8(protection_space.host));
  EXPECT_EQ(server_.port(),
            base::checked_cast<uint16_t>(protection_space.port));
  EXPECT_FALSE(protection_space.proxyType);
  EXPECT_NSEQ(NSURLProtectionSpaceHTTP, protection_space.protocol);
  EXPECT_NSEQ(NSURLAuthenticationMethodHTTPBasic,
              protection_space.authenticationMethod);

  // Cancel authentication and make sure that authentication is denied.
  std::move(auth_request->http_auth_callback)
      .Run(/*username=*/nil, /*password=*/nil);
  ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{
    return web_state()->GetTitle() == u"Denied: Missing Authorization Header";
  }));
}

}  // namespace web
