#!/usr/bin/env vpython3
# Copyright (c) 2023 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import os
import sys
from typing import List
import unittest

_THIS_DIR = os.path.abspath(os.path.dirname(__file__))
# The repo's root directory.
_ROOT_DIR = os.path.abspath(os.path.join(_THIS_DIR, "..", ".."))

# Add the repo's root directory for clearer imports.
sys.path.insert(0, _ROOT_DIR)

import metadata.fields.known as known_fields
import metadata.fields.field_types as field_types
import metadata.validation_result as vr


class FieldValidationTest(unittest.TestCase):
    def _run_field_validation(self,
                              field: field_types.MetadataField,
                              valid_values: List[str],
                              error_values: List[str],
                              warning_values: List[str] = []):
        """Helper to run a field's validation for different values."""
        for value in valid_values:
            self.assertIsNone(field.validate(value), value)

        for value in error_values:
            self.assertIsInstance(field.validate(value), vr.ValidationError,
                                  value)

        for value in warning_values:
            self.assertIsInstance(field.validate(value), vr.ValidationWarning,
                                  value)

    def test_freeform_text_validation(self):
        # Check validation of a freeform text field that should be on
        # one line.
        self._run_field_validation(
            field=field_types.SingleLineTextField("Text single line"),
            valid_values=["Text on single line", "a", "1"],
            error_values=["", "\n", " "],
        )

        # Check validation of a freeform text field that can span
        # multiple lines.
        self._run_field_validation(
            field=field_types.FreeformTextField("Freeform multi"),
            valid_values=[
                "This is text spanning multiple lines:\n"
                "    * with this point\n"
                "    * and this other point",
                "Text on single line",
                "a",
                "1",
            ],
            error_values=["", "\n", " "],
        )

    def test_yes_no_field_validation(self):
        self._run_field_validation(
            field=field_types.YesNoField("Yes/No test"),
            valid_values=["yes", "no", "No", "YES"],
            error_values=["", "\n", "Probably yes"],
            warning_values=["Yes?", "not"],
        )

    def test_cpe_prefix_validation(self):
        self._run_field_validation(
            field=known_fields.CPE_PREFIX,
            valid_values=[
                "unknown",
                "cpe:2.3:a:sqlite:sqlite:3.0.0:*:*:*:*:*:*:*",
                "cpe:2.3:a:sqlite:sqlite:*:*:*:*:*:*:*:*",
                "cpe:/a:vendor:product:version:update:edition:lang",
                "cpe:/a::product:",
                "cpe:/:vendor::::edition",
                "cpe:/:vendor",
            ],
            error_values=[
                "",
                "\n",
                "cpe:2.3:a:sqlite:sqlite:3.0.0",
                "cpe:2.3:a:sqlite:sqlite::::::::",
                "cpe:/",
                "cpe:/a:vendor:product:version:update:edition:lang:",
            ],
        )

    def test_date_validation(self):
        self._run_field_validation(
            field=known_fields.DATE,
            valid_values=["2012-03-04"],
            error_values=[
                "",
                "\n",
                "N/A",
                "03-04-12",  # Ambiguous month and day.
                "04/03/2012",  # Ambiguous month and day.
            ],
            warning_values=[
                "2012-03-04 UTC", "2012-03-04 UTC+10:00",
                "2012/03/04 UTC+10:00", "20120304", "April 3, 2012",
                "3 Apr 2012", "30/12/2000", "20-03-2020",
                "Tue Apr 3 05:06:07 2012 +0800"
            ],
        )

    def test_license_validation(self):
        self._run_field_validation(
            field=known_fields.LICENSE,
            valid_values=[
                "Apache, 2.0 / MIT / MPL 2",
                "LGPL 2.1",
                "GPL v2 or later",
                "LGPL2 with the classpath exception",
                "Apache, Version 2 and Public domain",
                "Public domain or MPL 2",
                "APSL 2 and the MIT license",
            ],
            error_values=["", "\n", ",", "Apache 2.0 / MIT / "],
            warning_values=[
                "Custom license",
                "Custom / MIT",
            ],
        )

    def test_license_file_validation(self):
        self._run_field_validation(
            field=known_fields.LICENSE_FILE,
            valid_values=[
                "LICENSE", "src/LICENSE.txt",
                "LICENSE, //third_party_test/LICENSE-TEST",
                "src/MISSING_LICENSE"
            ],
            error_values=["", "\n", ","],
            warning_values=["NOT_SHIPPED"],
        )

        # Check relative path from README directory, and multiple
        # license files.
        result = known_fields.LICENSE_FILE.validate_on_disk(
            value="LICENSE, src/LICENSE.txt",
            source_file_dir=os.path.join(_THIS_DIR, "data"),
            repo_root_dir=_THIS_DIR,
        )
        self.assertIsNone(result)

        # Check relative path from Chromium src directory.
        result = known_fields.LICENSE_FILE.validate_on_disk(
            value="//data/LICENSE",
            source_file_dir=os.path.join(_THIS_DIR, "data"),
            repo_root_dir=_THIS_DIR,
        )
        self.assertIsNone(result)

        # Check missing file.
        result = known_fields.LICENSE_FILE.validate_on_disk(
            value="MISSING_LICENSE",
            source_file_dir=os.path.join(_THIS_DIR, "data"),
            repo_root_dir=_THIS_DIR,
        )
        self.assertIsInstance(result, vr.ValidationError)

        # Check deprecated NOT_SHIPPED.
        result = known_fields.LICENSE_FILE.validate_on_disk(
            value="NOT_SHIPPED",
            source_file_dir=os.path.join(_THIS_DIR, "data"),
            repo_root_dir=_THIS_DIR,
        )
        self.assertIsInstance(result, vr.ValidationWarning)

    def test_url_validation(self):
        self._run_field_validation(
            field=known_fields.URL,
            valid_values=[
                "https://www.example.com/a",
                "http://www.example.com/b",
                "ftp://www.example.com/c,git://www.example.com/d",
                "https://www.example.com/a\n  https://example.com/b",
                "This is the canonical public repository",
            ],
            warning_values=[
                # Scheme is case-insensitive, but should be lower case.
                "Https://www.example.com/g",
            ],
            error_values=[
                "",
                "\n",
                "ghttps://www.example.com/e",
                "https://www.example.com/ f",
                "This is an unrecognized message for the URL",
            ],
        )

    def test_version_validation(self):
        self._run_field_validation(
            field=known_fields.VERSION,
            valid_values=["n / a", "123abc", "unknown forked version"],
            error_values=["", "\n"],
            warning_values=["0", "unknown"],
        )

    def test_local_modifications(self):
        # Checks local modifications field early terminates when we can reasonably infer there's no modification.
        _NO_MODIFICATION_VALUES = [
            "None", "None.", "N/A.", "(none).", "No modification", "\nNone."
        ]
        for value in _NO_MODIFICATION_VALUES:
            self.assertTrue(
                known_fields.LOCAL_MODIFICATIONS.should_terminate_field(value))

        # Checks ambiguous values won't early terminate the field.
        _MAY_CONTAIN_MODIFICATION_VALUES = [
            "None. Except doing something.",
            "Modify file X to include ....",
        ]
        for value in _MAY_CONTAIN_MODIFICATION_VALUES:
            self.assertFalse(
                known_fields.LOCAL_MODIFICATIONS.should_terminate_field(value))

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