Skip to content
50 changes: 50 additions & 0 deletions custom_components/hacs/validate/license.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from __future__ import annotations

from typing import TYPE_CHECKING

from .base import ActionValidationBase, ValidationException

if TYPE_CHECKING:
from ..repositories.base import HacsRepository

# GitHub license keys for commonly recognised open source licenses.
OPEN_SOURCE_LICENSES = {
"agpl-3.0",
"apache-2.0",
"bsd-2-clause",
"bsd-3-clause",
"bsl-1.0",
"cc0-1.0",
"epl-2.0",
"gpl-2.0",
"gpl-3.0",
"lgpl-2.1",
"mit",
"mpl-2.0",
"unlicense",
}


async def async_setup_validator(repository: HacsRepository) -> Validator:
"""Set up this validator."""
return Validator(repository=repository)


class Validator(ActionValidationBase):
"""Validate the repository."""

more_info = "https://hacs.xyz/docs/publish/include#check-repository"
allow_fork = False

async def async_validate(self) -> None:
"""Validate the repository."""
if (license_info := self.repository.repository_object.attributes.get("license")) is None:
raise ValidationException("The repository has no license")
if (license_key := license_info.get("key", "unknown")) not in OPEN_SOURCE_LICENSES:
raise ValidationException(
"The repository has no recognized open source license "
f"(license key is '{license_key}')"
)
self.repository.logger.debug(
"The repository has a valid license: %s", license_info.get("name")
)
2 changes: 1 addition & 1 deletion tests/action/test_hacs_action_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ async def test_hacs_action_integration(
await preflight()

assert (
"All (8) checks passed" if test_case["succeed"] else "1/8 checks failed") in caplog.text
"All (9) checks passed" if test_case["succeed"] else "1/9 checks failed") in caplog.text

splitlines = [f"<{line.rsplit(' <')[1]}" for line in caplog.text.split(
"\n") if " <" in line]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
<Validation information> completed
<Validation integration_manifest> failed: invalid url for dictionary value @ data['documentation']. Got None (More info: https://hacs.xyz/docs/publish/include#check-manifest )
<Validation issues> completed
<Validation license> completed
<Validation topics> completed
<Integration hacs-test-org/integration-basic> 1/8 checks failed
<Integration hacs-test-org/integration-basic> 1/9 checks failed
<Integration hacs-test-org/integration-basic> Validation completed
::group::data
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
<Validation information> completed
<Validation integration_manifest> failed: invalid url for dictionary value @ data['issue_tracker']. Got None (More info: https://hacs.xyz/docs/publish/include#check-manifest )
<Validation issues> completed
<Validation license> completed
<Validation topics> completed
<Integration hacs-test-org/integration-basic> 1/8 checks failed
<Integration hacs-test-org/integration-basic> 1/9 checks failed
<Integration hacs-test-org/integration-basic> Validation completed
::group::data
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
<Validation information> completed
<Validation integration_manifest> completed
<Validation issues> completed
<Validation license> completed
<Validation topics> completed
<Integration hacs-test-org/integration-basic> All (8) checks passed
<Integration hacs-test-org/integration-basic> All (9) checks passed
<Integration hacs-test-org/integration-basic> Validation completed
::group::data
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
<Validation information> completed
<Validation integration_manifest> completed
<Validation issues> completed
<Validation license> completed
<Validation topics> completed
<Integration hacs-test-org/integration-basic> All (8) checks passed
<Integration hacs-test-org/integration-basic> All (9) checks passed
<Integration hacs-test-org/integration-basic> Validation completed
::group::data
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
<Validation information> completed
<Validation integration_manifest> completed
<Validation issues> completed
<Validation license> completed
<Validation topics> completed
<Integration hacs-test-org/integration-basic> All (8) checks passed
<Integration hacs-test-org/integration-basic> All (9) checks passed
<Integration hacs-test-org/integration-basic> Validation completed
::group::data
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"tests/validate/test_repository_license_check.py::test_repository_no_license": {
"https://api.github.com/repos/hacs/integration": 1,
"https://api.github.com/repos/hacs/integration/contents/custom_components/hacs/manifest.json": 1,
"https://api.github.com/repos/hacs/integration/contents/hacs.json": 1,
"https://api.github.com/repos/hacs/integration/git/trees/main": 1,
"https://api.github.com/repos/hacs/integration/releases": 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"tests/validate/test_repository_license_check.py::test_repository_non_oss_license": {
"https://api.github.com/repos/hacs/integration": 1,
"https://api.github.com/repos/hacs/integration/contents/custom_components/hacs/manifest.json": 1,
"https://api.github.com/repos/hacs/integration/contents/hacs.json": 1,
"https://api.github.com/repos/hacs/integration/git/trees/main": 1,
"https://api.github.com/repos/hacs/integration/releases": 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"tests/validate/test_repository_license_check.py::test_repository_unrecognized_license": {
"https://api.github.com/repos/hacs/integration": 1,
"https://api.github.com/repos/hacs/integration/contents/custom_components/hacs/manifest.json": 1,
"https://api.github.com/repos/hacs/integration/contents/hacs.json": 1,
"https://api.github.com/repos/hacs/integration/git/trees/main": 1,
"https://api.github.com/repos/hacs/integration/releases": 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"tests/validate/test_repository_license_check.py::test_repository_valid_license[agpl-3.0]": {
"https://api.github.com/repos/hacs/integration": 1,
"https://api.github.com/repos/hacs/integration/contents/custom_components/hacs/manifest.json": 1,
"https://api.github.com/repos/hacs/integration/contents/hacs.json": 1,
"https://api.github.com/repos/hacs/integration/git/trees/main": 1,
"https://api.github.com/repos/hacs/integration/releases": 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"tests/validate/test_repository_license_check.py::test_repository_valid_license[unlicense]": {
"https://api.github.com/repos/hacs/integration": 1,
"https://api.github.com/repos/hacs/integration/contents/custom_components/hacs/manifest.json": 1,
"https://api.github.com/repos/hacs/integration/contents/hacs.json": 1,
"https://api.github.com/repos/hacs/integration/git/trees/main": 1,
"https://api.github.com/repos/hacs/integration/releases": 1
}
}
44 changes: 44 additions & 0 deletions tests/validate/test_repository_license_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from unittest.mock import MagicMock

import pytest

from custom_components.hacs.validate.license import OPEN_SOURCE_LICENSES, Validator


async def test_repository_no_license(repository):
repository.repository_object = MagicMock()
repository.repository_object.attributes = {"license": None}
check = Validator(repository)
await check.execute_validation()
assert check.failed


async def test_repository_unrecognized_license(repository):
repository.repository_object = MagicMock()
repository.repository_object.attributes = {
"license": {"key": "other", "name": "Other", "spdx_id": "NOASSERTION"},
}
check = Validator(repository)
await check.execute_validation()
assert check.failed


async def test_repository_non_oss_license(repository):
repository.repository_object = MagicMock()
repository.repository_object.attributes = {
"license": {"key": "cc-by-nc-4.0", "name": "Creative Commons Attribution Non Commercial 4.0", "spdx_id": "CC-BY-NC-4.0"},
Comment thread
ludeeus marked this conversation as resolved.
}
check = Validator(repository)
await check.execute_validation()
assert check.failed


@pytest.mark.parametrize("license_key", [sorted(OPEN_SOURCE_LICENSES)[0], sorted(OPEN_SOURCE_LICENSES)[-1]])
async def test_repository_valid_license(repository, license_key):
repository.repository_object = MagicMock()
repository.repository_object.attributes = {
"license": {"key": license_key, "name": f"License {license_key}", "spdx_id": license_key.upper()},
}
check = Validator(repository)
await check.execute_validation()
assert not check.failed
Loading