#!/usr/bin/env vpython3
# Copyright 2022 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""File for testing serve_repo.py."""

import argparse
import json
import unittest
import unittest.mock as mock

from types import SimpleNamespace

import serve_repo

from common import REPO_ALIAS, register_device_args

_REPO_DIR = 'test_repo_dir'
_REPO_NAME = 'test_repo_name'
_TARGET = 'test_target'
_NO_SERVERS_LIST = json.dumps({'ok': {'data': []}})
_SERVERS_LIST = json.dumps(
    {'ok': {'data': [{'name': _REPO_NAME, 'repo_path': _REPO_DIR}]}}
)
_WRONG_SERVERS_LIST = json.dumps(
    {'ok': {'data': [{'name': 'wrong_name', 'repo_path': _REPO_DIR}]}}
)


# Tests private functions.
# pylint: disable=protected-access
class ServeRepoTest(unittest.TestCase):
    """Unittests for serve_repo.py."""

    @mock.patch('serve_repo.run_ffx_command')
    def test_start_server(self, mock_ffx) -> None:
        """Test |_start_serving| function for start."""

        mock_ffx.side_effect = [
            SimpleNamespace(returncode=1, stderr='err1', stdout=''),
            SimpleNamespace(returncode=0, stdout=_SERVERS_LIST, stderr=''),
            SimpleNamespace(returncode=0, stdout=_SERVERS_LIST, stderr=''),
            SimpleNamespace(returncode=0, stdout='', stderr=''),
        ]
        serve_repo._start_serving(_REPO_DIR, _REPO_NAME, _TARGET)
        self.assertEqual(mock_ffx.call_count, 3)
        first_call = mock_ffx.call_args_list[0]

        self.assertEqual(
            mock.call(
                cmd=[
                    'repository',
                    'server',
                    'start',
                    '--background',
                    '--address',
                    '[::]:0',
                    '--repository',
                    _REPO_NAME,
                    '--repo-path',
                    _REPO_DIR,
                    '--no-device',
                ],
                check=False,
            ),
            first_call,
        )
        second_call = mock_ffx.call_args_list[1]
        self.assertEqual(
            mock.call(
                cmd=[
                    '--machine',
                    'json',
                    'repository',
                    'server',
                    'list',
                    '--name',
                    _REPO_NAME,
                ],
                check=False,
                capture_output=True,
            ),
            second_call,
        )

        third_call = mock_ffx.call_args_list[2]
        self.assertEqual(
            mock.call(
                cmd=[
                    'target',
                    'repository',
                    'register',
                    '-r',
                    _REPO_NAME,
                    '--alias',
                    REPO_ALIAS,
                ],
                target_id=_TARGET,
            ),
            third_call,
        )

    @mock.patch('serve_repo.run_ffx_command')
    def test_assert_server_running(self, mock_ffx) -> None:
        """Test |_assert_server_running| function for start."""

        mock_ffx.side_effect = [
            SimpleNamespace(returncode=0, stdout=_SERVERS_LIST)
        ]
        # Raises an error if there is a problem, so no need to check for
        # RuntimeError.
        try:
            serve_repo._assert_server_running(_REPO_NAME)
        except RuntimeError as err:
            self.fail(f'Unexpected error: {err}')

    @mock.patch('serve_repo.run_ffx_command')
    def test_is_server_not_running(self, mock_ffx) -> None:
        """Test |_assert_server_running| function for start with no server."""

        mock_ffx.return_value = SimpleNamespace(
            returncode=0, stdout=_NO_SERVERS_LIST, stderr=''
        )

        with self.assertRaises(RuntimeError):
            serve_repo._assert_server_running(_REPO_NAME)

    @mock.patch('serve_repo.run_ffx_command')
    def test_is_wrong_server_running(self, mock_ffx) -> None:
        """Test |_assert_server_running| function for start with no server."""

        mock_ffx.return_value = SimpleNamespace(
            returncode=0, stdout=_WRONG_SERVERS_LIST, stderr=''
        )

        with self.assertRaises(RuntimeError):
            serve_repo._assert_server_running(_REPO_NAME)

    @mock.patch('serve_repo.run_ffx_command')
    def test_is_server_not_running_bad_ffx(self, mock_ffx) -> None:
        """Test |_assert_server_running| function for start with bad ffx."""

        mock_ffx.return_value = SimpleNamespace(
            returncode=1, stderr='Some error', stdout=''
        )

        with self.assertRaises(RuntimeError):
            serve_repo._assert_server_running(_REPO_NAME)

    @mock.patch('serve_repo.run_ffx_command')
    def test_is_server_not_running_bad_json(self, mock_ffx) -> None:
        """Test |_assert_server_running| function for start with bad ffx."""

        mock_ffx.return_value = SimpleNamespace(
            returncode=0, stderr='', stdout='{"some": bad...'
        )

        with self.assertRaises(RuntimeError):
            serve_repo._assert_server_running(_REPO_NAME)

    @mock.patch('serve_repo.run_ffx_command')
    def test_stop_server(self, mock_ffx) -> None:
        """Test |_stop_serving| function for stop."""

        serve_repo._stop_serving(_REPO_NAME, _TARGET)
        self.assertEqual(mock_ffx.call_count, 2)
        first_call = mock_ffx.call_args_list[0]

        self.assertEqual(
            mock.call(
                cmd=['target', 'repository', 'deregister', '-r', _REPO_NAME],
                target_id=_TARGET,
                check=False,
            ),
            first_call,
        )
        second_call = mock_ffx.call_args_list[1]
        self.assertEqual(
            mock.call(
                cmd=['repository', 'server', 'stop', _REPO_NAME], check=False
            ),
            second_call,
        )

    @mock.patch('serve_repo._start_serving')
    @mock.patch('serve_repo._stop_serving')
    def test_serve_repository(self, mock_stop, mock_start) -> None:
        """Tests |serve_repository| context manager."""

        parser = argparse.ArgumentParser()
        serve_repo.register_serve_args(parser)
        register_device_args(parser)
        with serve_repo.serve_repository(
            parser.parse_args(
                [
                    '--repo',
                    _REPO_DIR,
                    '--repo-name',
                    _REPO_NAME,
                    '--target-id',
                    _TARGET,
                ]
            )
        ):
            self.assertEqual(mock_start.call_count, 1)
        self.assertEqual(mock_stop.call_count, 1)


if __name__ == '__main__':
    unittest.main()
