# Copyright © The Debusine Developers
# See the AUTHORS file at the top-level directory of this distribution
#
# This file is part of Debusine. It is subject to the license terms
# in the LICENSE file found in the top-level directory of this
# distribution. No part of Debusine, including this file, may be copied,
# modified, propagated, or distributed except according to the terms
# contained in the LICENSE file.

"""Tests for debusine.tests.base.TestCase."""

import logging
import os
from configparser import ConfigParser

import requests
import responses

from debusine.test.base import TestCase


class TestCaseTests(TestCase):
    """Tests for methods in debusine.test.base.TestCase."""

    def test_create_temp_config_directory(self) -> None:
        """create_temp_config_directory write the configuration."""
        config = {
            'General': {'default-server': 'debian'},
            'server:debian': {
                'url': 'https://debusine.debian.org',
                'token': 'token-for-debian',
            },
        }
        directory = self.create_temp_config_directory(config)

        actual_config = ConfigParser()
        actual_config.read(os.path.join(directory, 'config.ini'))

        expected_config = ConfigParser()
        expected_config.read_dict(config)

        self.assertEqual(actual_config, expected_config)

    def test_assert_dict_contains_subset_raises_exception(self) -> None:
        """Raise an exception (subset not in dictionary)."""
        expected_message = "{'b': 1} does not contain the subset {'a': 1}"
        with self.assertRaisesRegex(self.failureException, expected_message):
            self.assertDictContainsAll({'b': 1}, {'a': 1})

    def test_assert_dict_use_error_msg(self) -> None:
        """Raise exception using a specific error message."""
        expected_message = 'Missing values'

        with self.assertRaisesRegex(self.failureException, expected_message):
            self.assertDictContainsAll({}, {'a': 1}, expected_message)

    def test_assert_dict_contains_subset_does_not_raise_exception(self) -> None:
        """Do not raise any exception (subset in dictionary)."""
        self.assertDictContainsAll({'a': 1, 'b': 2}, {'a': 1})

    def test_assert_dict_contains_subset_arg1_not_a_dictionary(self) -> None:
        """Raise exception because of wrong type argument 1."""
        expected_message = (
            "'a' is not an instance of <class 'dict'> : "
            "First argument is not a dictionary"
        )
        with self.assertRaisesRegex(self.failureException, expected_message):
            self.assertDictContainsAll('a', {})  # type: ignore[arg-type]

    def test_assert_dict_contains_subset_arg2_not_a_dictionary(self) -> None:
        """Raise exception because of wrong type argument 2."""
        expected_message = (
            "'b' is not an instance of <class 'dict'> : "
            "Second argument is not a dictionary"
        )
        with self.assertRaisesRegex(self.failureException, expected_message):
            self.assertDictContainsAll({}, 'b')  # type: ignore[arg-type]

    def test_assert_raises_system_exit_no_system_exit(self) -> None:
        """Raise self.failureException because missing SystemExit."""
        expected_message = (
            r'SystemExit not raised : Did not raise '
            r'SystemExit with exit_code=\^3\$'
        )
        with self.assertRaisesRegex(self.failureException, expected_message):
            with self.assertRaisesSystemExit(3):
                pass

    def test_assert_raises_system_exit_unexpected_exit_code(self) -> None:
        """Raise self.failureException because wrong exit_code in SystemExit."""
        expected_message = (
            r'\^3\$" does not match "7" : Did not raise '
            r'SystemExit with exit_code=\^3\$'
        )
        with self.assertRaisesRegex(self.failureException, expected_message):
            with self.assertRaisesSystemExit(3):
                raise SystemExit(7)

    def test_assert_raises_system_exit_success(self) -> None:
        """Do not raise self.failureException: expected SystemExit is raised."""
        with self.assertRaisesSystemExit(3):
            raise SystemExit(3)

    @responses.activate
    def test_assert_token_key_included_in_all_requests(self) -> None:
        """Do not raise any exception (all requests had the token key)."""
        responses.add(
            responses.GET,
            'https://example.net/something',
        )
        responses.add(
            responses.GET,
            'https://example.net/something',
        )

        token_key = 'some-key'

        requests.get(
            'https://example.net/something',
            headers={'Token': token_key},
        )

        self.assert_token_key_included_in_all_requests(token_key)

    @responses.activate
    def test_assert_token_key_included_in_all_requests_raise_missing(
        self,
    ) -> None:
        """Raise exception because token not included in the request."""
        responses.add(
            responses.GET,
            'https://example.net/something',
        )

        requests.get('https://example.net/something')

        expected_message = (
            "Token missing in the headers for the request "
            "'https://example.net/something'"
        )

        with self.assertRaisesRegex(self.failureException, expected_message):
            self.assert_token_key_included_in_all_requests('some-token')

    @responses.activate
    def test_assert_token_key_included_in_all_requests_raise_mismatch(
        self,
    ) -> None:
        """Raise exception because token mismatch included in the request."""
        responses.add(
            responses.GET,
            'https://example.net/something',
        )

        token = 'token-for-server'

        requests.get(
            'https://example.net/something',
            headers={'Token': 'some-invalid-token'},
        )

        expected_message = (
            "Unexpected token. In the request: "
            "'https://example.net/something' "
            "Actual: 'some-invalid-token' Expected: 'token-for-server'"
        )

        with self.assertRaisesRegex(self.failureException, expected_message):
            self.assert_token_key_included_in_all_requests(token)

    def test_assertLogsContains_log_found(self) -> None:
        """assertLogsContains() does not raise self.failureException."""
        with self.assertLogsContains('required-log') as logs:
            logging.warning('required-log')

        self.assertEqual(logs.output, ["WARNING:root:required-log"])

    def test_assertLogsContains_log_expected_count_wrong(self) -> None:
        """assertLogsContains() raise self.failureException (wrong count)."""
        expected_message = (
            '^Expected: "required-log"\n'
            'Actual: "WARNING:root:required-log"\n'
            'Expected msg found 1 times, expected 2 times$'
        )

        with self.assertRaisesRegex(self.failureException, expected_message):
            with self.assertLogsContains('required-log', expected_count=2):
                logging.warning('required-log')

    def test_assertLogsContains_log_expected_not_found(self) -> None:
        """assertLogsContains() raise self.failureException (wrong count)."""
        expected_message = (
            '^Expected: "required-log"\n'
            'Actual: "WARNING:root:some-log"\n'
            'Expected msg found 0 times, expected 1 times$'
        )

        with self.assertRaisesRegex(self.failureException, expected_message):
            with self.assertLogsContains('required-log'):
                logging.warning('some-log')

    def test_assertLogsContains_log_expected_not_found_wrong_level(
        self,
    ) -> None:
        """assertLogsContains() raise self.failureException (wrong level)."""
        expected_message = (
            'no logs of level WARNING or higher triggered on root'
        )

        with self.assertRaisesRegex(self.failureException, expected_message):
            with self.assertLogsContains('required-log', level=logging.WARNING):
                logging.debug('some-log')

    def test_assertLogsContains_log_not_found_in_raise_exception(self) -> None:
        """
        assertLogsContains() raise self.failureException.

        It handles raised exceptions in the context code.
        """
        expected_message = (
            '^Expected: "The wanted message"\n'
            'Actual: "WARNING:root:Unrelated message"\n'
            'Expected msg found 0 times, expected 1 times$'
        )

        with self.assertRaisesRegex(self.failureException, expected_message):
            with (
                self.assertRaisesRegex(SystemExit, '3'),
                self.assertLogsContains('The wanted message'),
            ):
                logging.warning('Unrelated message')
                raise SystemExit(3)
