#!/usr/bin/env python3
# Copyright 2022 The Pigweed Authors
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
"""Tests for gitmodules."""

from pathlib import Path
import tempfile
import unittest
from unittest.mock import MagicMock

from pw_presubmit import gitmodules, PresubmitFailure


def dotgitmodules(
    name: str = 'foo',
    url: str | None = None,
    host: str | None = None,
    branch: str | None = 'main',
):
    cfg = f'[submodule "{name}"]\n'
    cfg += f'path = {name}\n'
    if url is None and host is None:
        host = 'host'
    if host:
        cfg += f'url = https://{host}.googlesource.com/{name}\n'
    else:
        assert url
        cfg += f'url = {url}\n'
    if branch:
        cfg += f'branch = {branch}\n'
    return cfg


class TestGitmodules(unittest.TestCase):
    """Test gitmodules check."""

    def setUp(self):
        self.ctx: MagicMock = None

    def _run(self, config: gitmodules.Config, contents: str) -> None:
        self.ctx = MagicMock()
        self.ctx.fail = MagicMock()

        with tempfile.TemporaryDirectory() as tempdir:
            path = Path(tempdir) / '.gitmodules'
            with path.open('w') as outs:
                outs.write(contents)

            gitmodules.process_gitmodules(self.ctx, config, path)

    def test_ok_no_submodules(self) -> None:
        self._run(gitmodules.Config(), '')
        self.ctx.fail.assert_not_called()

    def test_no_submodules_allowed(self) -> None:
        self._run(
            gitmodules.Config(allow_submodules=False),
            dotgitmodules(url='../foo'),
        )
        self.ctx.fail.assert_called()

    def test_ok_default(self) -> None:
        self._run(gitmodules.Config(), dotgitmodules(url='../foo'))
        self.ctx.fail.assert_not_called()

    def test_ok_restrictive(self) -> None:
        cfg: gitmodules.Config = gitmodules.Config(
            allow_non_googlesource_hosts=False,
            allowed_googlesource_hosts=('host',),
            require_relative_urls=True,
            allow_sso=False,
            allow_git_corp_google_com=False,
            require_branch=True,
        )
        self._run(cfg, dotgitmodules(url='../foo'))
        self.ctx.fail.assert_not_called()

    def test_validate_ok(self) -> None:
        def validator(ctx, path, name, props) -> None:
            _ = name
            if 'bad' in props['url']:
                ctx.fail('bad', path)

        cfg: gitmodules.Config = gitmodules.Config(validator=validator)
        self._run(cfg, dotgitmodules(host='host'))
        self.ctx.fail.assert_not_called()

    def test_validate_fail(self) -> None:
        def validator(ctx, path, name, props) -> None:
            _ = name
            if 'bad' in props['url']:
                ctx.fail('bad', path)

        cfg: gitmodules.Config = gitmodules.Config(validator=validator)
        self._run(cfg, dotgitmodules(host='badhost'))
        self.ctx.fail.assert_called()

    def test_non_google_ok(self) -> None:
        cfg: gitmodules.Config = gitmodules.Config(
            allow_non_googlesource_hosts=True
        )
        self._run(cfg, dotgitmodules(url='https://github.com/foo/bar'))
        self.ctx.fail.assert_not_called()

    def test_non_google_fail(self) -> None:
        cfg: gitmodules.Config = gitmodules.Config(
            allow_non_googlesource_hosts=False
        )
        self._run(cfg, dotgitmodules(url='https://github.com/foo/bar'))
        self.ctx.fail.assert_called()

    def test_bad_allowed_googlesource_hosts(self) -> None:
        with self.assertRaises(PresubmitFailure):
            cfg: gitmodules.Config = gitmodules.Config(
                allowed_googlesource_hosts=('pigweed-review',)
            )
            self._run(cfg, dotgitmodules())

    def test_bad_type_allowed_googlesource_hosts(self) -> None:
        with self.assertRaises(AssertionError):
            cfg: gitmodules.Config = gitmodules.Config(
                allowed_googlesource_hosts=('pigweed')
            )
            self._run(cfg, dotgitmodules())

    def test_allowed_googlesource_hosts_ok(self) -> None:
        cfg: gitmodules.Config = gitmodules.Config(
            allowed_googlesource_hosts=(
                'pigweed',
                'pigweed-internal',
            )
        )
        self._run(cfg, dotgitmodules(host='pigweed-internal'))
        self.ctx.fail.assert_not_called()

    def test_allowed_googlesource_hosts_fail(self) -> None:
        cfg: gitmodules.Config = gitmodules.Config(
            allowed_googlesource_hosts=('pigweed-internal',)
        )
        self._run(cfg, dotgitmodules(host='pigweed'))
        self.ctx.fail.assert_called()

    def test_require_relative_urls_ok(self) -> None:
        cfg: gitmodules.Config = gitmodules.Config(require_relative_urls=False)
        self._run(cfg, dotgitmodules(host='foo'))
        self.ctx.fail.assert_not_called()

    def test_require_relative_urls_fail(self) -> None:
        cfg: gitmodules.Config = gitmodules.Config(require_relative_urls=True)
        self._run(cfg, dotgitmodules(host='foo'))
        self.ctx.fail.assert_called()

    def test_allow_sso_ok(self) -> None:
        cfg: gitmodules.Config = gitmodules.Config(allow_sso=True)
        self._run(cfg, dotgitmodules(url='sso://host/foo'))
        self.ctx.fail.assert_not_called()

    def test_allow_sso_fail(self) -> None:
        cfg: gitmodules.Config = gitmodules.Config(allow_sso=False)
        self._run(cfg, dotgitmodules(url='sso://host/foo'))
        self.ctx.fail.assert_called()

    def test_allow_git_corp_google_com_ok(self) -> None:
        cfg: gitmodules.Config = gitmodules.Config(
            allow_git_corp_google_com=True
        )
        self._run(cfg, dotgitmodules(url='https://foo.git.corp.google.com/bar'))
        self.ctx.fail.assert_not_called()

    def test_allow_git_corp_google_com_fail(self) -> None:
        cfg: gitmodules.Config = gitmodules.Config(
            allow_git_corp_google_com=False
        )
        self._run(cfg, dotgitmodules(url='https://foo.git.corp.google.com/bar'))
        self.ctx.fail.assert_called()

    def test_require_branch_ok(self) -> None:
        cfg: gitmodules.Config = gitmodules.Config(require_branch=False)
        self._run(cfg, dotgitmodules(branch=None))
        self.ctx.fail.assert_not_called()

    def test_require_branch_fail(self) -> None:
        cfg: gitmodules.Config = gitmodules.Config(require_branch=True)
        self._run(cfg, dotgitmodules(branch=None))
        self.ctx.fail.assert_called()


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