#!/usr/bin/env python
"""Tests for the main content view."""

from absl import app

from grr_response_server import client_index
from grr_response_server import data_store
from grr_response_server.gui import gui_test_lib
from grr_response_server.rdfvalues import mig_objects
from grr.test_lib import hunt_test_lib
from grr.test_lib import test_lib


class TestClientSearch(
    gui_test_lib.SearchClientTestBase, hunt_test_lib.StandardHuntTestMixin
):

  def setUp(self):
    super().setUp()
    self._CreateClients()

  def _CreateClients(self):
    # To test all search keywords, we can rely on SetupClients
    # creating clients with attributes containing a numeric
    # value, e.g. hostname will be Host-0, Host-1, etc.
    self.client_ids = self.SetupClients(15)

    self.AddClientLabel(
        self.client_ids[0], self.test_username, "common_test_label"
    )
    self.AddClientLabel(
        self.client_ids[0], self.test_username, "unique_test_label"
    )
    self.AddClientLabel(
        self.client_ids[1], self.test_username, "common_test_label"
    )

    snapshot = data_store.REL_DB.ReadClientSnapshot(self.client_ids[0])
    snapshot.knowledge_base.users.add(username="sample_user")
    snapshot.knowledge_base.users.add(username=self.test_username)
    data_store.REL_DB.WriteClientSnapshot(snapshot)
    client_index.ClientIndex().AddClient(
        mig_objects.ToRDFClientSnapshot(
            data_store.REL_DB.ReadClientSnapshot(self.client_ids[0])
        )
    )

  def _WaitForSearchResults(self, target_count):
    self.WaitUntil(self.IsElementPresent, "css=grr-clients-list")

    if not target_count:
      # When we expect no elements at all, we have to check that Loading...
      # disappears and then nothing is displayed.
      self.WaitUntilNot(self.IsElementPresent, "css=tr:contains('Loading...')")
      self.WaitUntilEqual(
          target_count, self.GetCssCount, "css=grr-clients-list tbody > tr"
      )
    else:
      # When expect multiple elements we can just count all the rows that have
      # no "Loading..." init. Handling this case separately from target_count==0
      # case, since if we have enough elements in the result list, "Loading..."
      # label will go off-screen and therefore won't be removed from DOM
      # by Angular code (it has to actually be visible in order to be removed).
      self.WaitUntilEqual(
          target_count,
          self.GetCssCount,
          "css=grr-clients-list tbody > tr:not(:contains('Loading...'))",
      )

  def testPageTitleChangesAccordingToQuery(self):
    self.Open("/legacy#/search?q=foo")
    self.WaitUntilEqual('GRR | Search for "foo"', self.GetPageTitle)

    self.Type("client_query", text="host:Host-1", end_with_enter=True)
    self.WaitUntilEqual('GRR | Search for "host:Host-1"', self.GetPageTitle)

    # Not entering any search term checks for all clients.
    self.Open("/legacy#/search")
    self.WaitUntilEqual("GRR | Client List", self.GetPageTitle)

  def testEmptySearchShowsAllClients(self):
    self.Open("/legacy")
    self.Click("client_query_submit")

    # All 15 clients (10 default clients + 5 from setUp()) need to be found.
    self._WaitForSearchResults(target_count=15)

  def testSearchByClientId(self):
    client_name = self.client_ids[0]

    self.Open("/legacy")
    self.Type("client_query", text=client_name, end_with_enter=True)

    self.WaitUntil(
        self.IsElementPresent,
        "css=grr-clients-list tr:contains('%s') " % client_name,
    )
    self._WaitForSearchResults(target_count=1)

  def testSearchWithHostKeyword(self):
    self.Open("/legacy")

    # Host-1 exists, so we should find exactly one item.
    self.Type("client_query", text="host:Host-1", end_with_enter=True)

    self._WaitForSearchResults(target_count=1)

    # Host-99 does not exists, so we shouldn't find anything.
    self.Type("client_query", text="host:Host-99", end_with_enter=True)

    self._WaitForSearchResults(target_count=0)

    # The host keyword also searches FQDN, so we should find an item.
    self.Type(
        "client_query", text="host:Host-1.example.com", end_with_enter=True
    )

    self._WaitForSearchResults(target_count=1)

    # Unknown fqdns should yield an empty result.
    self.Type(
        "client_query", text="host:Host-99.example.com", end_with_enter=True
    )

    self._WaitForSearchResults(target_count=0)

  def testSearchWithLabelKeyword(self):
    self.Open("/legacy")

    # Several client have this label, so we should find them all.
    self.Type(
        "client_query", text="label:common_test_label", end_with_enter=True
    )

    self._WaitForSearchResults(target_count=2)

    # Only one client has the unique label.
    self.Type(
        "client_query", text="label:unique_test_label", end_with_enter=True
    )

    self._WaitForSearchResults(target_count=1)

    # Only one client has the unique label.
    self.Type("client_query", text="label:unused_label", end_with_enter=True)

    self._WaitForSearchResults(target_count=0)

  def testSearchWithIPKeyword(self):
    self.Open("/legacy")

    # IP contains the client number, so we should find exactly one item.
    self.Type("client_query", text="ip:192.168.0.1", end_with_enter=True)

    self._WaitForSearchResults(target_count=1)

    # Unknown ips should yield an empty result.
    self.Type("client_query", text="ip:192.168.32.0", end_with_enter=True)

    self._WaitForSearchResults(target_count=0)

  def testSearchWithMacKeyword(self):
    self.Open("/legacy")

    # Mac contains the client number, so we should find exactly one item.
    self.Type("client_query", text="aabbccddee01", end_with_enter=True)

    self._WaitForSearchResults(target_count=1)

    # Unknown ips should yield an empty result.
    self.Type("client_query", text="aabbccddee99", end_with_enter=True)

    self._WaitForSearchResults(target_count=0)

  def testSearchWithUserKeyword(self):
    self.Open("/legacy")

    self.Type(
        "client_query", text="user:" + self.test_username, end_with_enter=True
    )

    self._WaitForSearchResults(target_count=1)

    # Unknown users should yield an empty result.
    self.Type("client_query", text="user:I_do_not_exist", end_with_enter=True)

    self._WaitForSearchResults(target_count=0)

  def testSearchingForHuntIdOpensHunt(self):
    hunt_id = self.StartHunt(description="demo hunt")

    self.Open("/legacy")
    self.Type("client_query", text=hunt_id, end_with_enter=True)
    self.WaitUntil(self.IsTextPresent, "GenericHunt")
    # This checks that Overview tab actually got updated.
    self.WaitUntil(self.IsTextPresent, "Total Network Traffic")

  def testSearchingForNonExistingHuntIdPerformsClientSearch(self):
    self.Open("/legacy")

    # Hunt does not exist, so a client search should be performed.
    self.Type("client_query", text="H:12345678", end_with_enter=True)

    self._WaitForSearchResults(target_count=0)

  def testSearchingForLabelOpensTypeAheadDropdown(self):
    self.Open("/legacy")

    # We need to retry the whole sequence of "clear->type->wait for dropdown",
    # as there's a potential race when we start typing before the
    # typeahead options are loaded. In this case the dropdown won't be shown,
    # and we need to type in the data one more time.
    def TypeAndGetDropdownCount():
      self.Type("client_query", text="")
      self.Type("client_query", text="common_")
      return self.GetCssCount("css=grr-search-box ul.dropdown-menu li")

    self.WaitUntilEqual(1, TypeAndGetDropdownCount)

    # If the popup is visible, check that clicking it will change the search
    # input.
    self.Click("css=grr-search-box ul.dropdown-menu li")
    self.WaitUntilEqual(
        "label:common_test_label", self.GetValue, "css=#client_query"
    )

  def testBackButtonWorksAsExpected(self):
    self.RequestAndGrantClientApproval(self.client_ids[0])

    client_name = self.client_ids[0]

    self.Open("/legacy#/clients/" + client_name)
    self.WaitUntil(self.IsTextPresent, client_name)
    # Check that correct navigation link is selected.
    self.WaitUntil(
        self.IsElementPresent, "css=.active > a[grrtarget='client.hostInfo']"
    )

    self.Click("css=a[grrtarget='client.launchFlows']")
    self.WaitUntil(self.IsTextPresent, "Please Select a flow to launch")
    # Check that correct navigation link is selected.
    self.WaitUntil(
        self.IsElementPresent, "css=.active > a[grrtarget='client.launchFlows']"
    )

    # Back button should bring us to host information again.
    self.Back()
    self.WaitUntil(self.IsTextPresent, client_name)
    # Check that correct navigation link is selected.
    self.WaitUntil(
        self.IsElementPresent, "css=.active > a[grrtarget='client.hostInfo']"
    )

    # Forward button should bring us to launch flows again.
    self.Forward()
    self.WaitUntil(self.IsTextPresent, "Please Select a flow to launch")
    # Check that correct navigation link is selected.
    self.WaitUntil(
        self.IsElementPresent, "css=.active > a[grrtarget='client.launchFlows']"
    )

  def testSuggestedReasonIsPropagatedFromSearchToApproval(self):
    client_id = self.SetupClient(0)

    self.Open("/legacy#/search?q=.&reason=t123")
    self.WaitUntilEqual('GRR | Search for "."', self.GetPageTitle)

    self.Click("css=tr:contains('{}')".format(client_id))

    self.Click("css=button[name=requestApproval]")

    self.WaitUntilEqual("t123", self.GetValue, "css=input[name=acl_reason]")


class TestDefaultGUISettings(gui_test_lib.GRRSeleniumTest):

  def testDefaultGUISettingsWork(self):
    data_store.REL_DB.DeleteGRRUser(self.test_username)

    self.Open(
        "/legacy"
    )  # The ui displays an error here if the settings are invalid.

    self.WaitUntil(self.IsTextPresent, "Welcome to GRR")

    self.Click("css=grr-user-settings-button button")
    self.WaitUntil(self.IsTextPresent, "Settings")

    self.WaitUntil(self.IsTextPresent, "Mode")
    self.WaitUntilEqual(
        "BASIC (default)",
        self.GetSelectedLabel,
        "css=label:contains('Mode') ~ div.controls select",
    )


if __name__ == "__main__":
  app.run(test_lib.main)
