Repository: OBOFoundry/OBOFoundry.github.io

Issue:
Add automated check for licenses
              Did someone carefully check the repository? It says there the data is licensed under CC BY 4.0-SA, which is not acceptable for OBO Foundry: https://github.com/Dalalghamdi/NGBO

_Originally posted by @cthoyt in https://github.com/OBOFoundry/OBOFoundry.github.io/issues/2214#issuecomment-1399241788_
            

What happened in NGBO is that the author had a different license in the metadata they submitted to the OBO Foundry than what the LICENSE file said inside their repository. Further, this wasn't carefully checked by reviewers. We should add an automated check for the future to make sure this doesn't happen again.


Discussion:

Resolving patch:
diff --git a/src/obofoundry/utils.py b/src/obofoundry/utils.py
--- a/src/obofoundry/utils.py
+++ b/src/obofoundry/utils.py
@@ -3,11 +3,12 @@
 import requests
 import yaml
 
-from obofoundry.constants import ONTOLOGY_DIRECTORY
+from obofoundry.constants import ONTOLOGY_DIRECTORY, ROOT
 
 __all__ = [
     "get_data",
     "query_wikidata",
+    "get_new_data",
 ]
 
 
@@ -41,3 +42,20 @@ def query_wikidata(query: str):
     res.raise_for_status()
     res_json = res.json()
     return res_json["results"]["bindings"]
+
+
+def get_new_data():
+    """Get records for ontologies that have additional checks.
+
+    So far, this applies in the following scenarios:
+
+    1. New ontologies, i.e., there's a markdown file for the ontology in the ``/ontologies`` directory
+       but has it not yet been published and does not appear in the config.yml
+    """
+    data = get_data()
+    config_path = ROOT.joinpath("_config.yml")
+    config_data = yaml.safe_load(config_path.read_text())
+    published = {record["id"] for record in config_data["ontologies"]}
+    return {
+        prefix: record for prefix, record in data.items() if prefix not in published
+    }


Test patch:
diff --git a/tests/test_integrity.py b/tests/test_integrity.py
--- a/tests/test_integrity.py
+++ b/tests/test_integrity.py
@@ -2,6 +2,7 @@
 
 import json
 import unittest
+from functools import lru_cache
 from io import StringIO
 from pathlib import Path
 from typing import Set
@@ -10,7 +11,7 @@
 import yaml
 
 from obofoundry.standardize_metadata import ModifiedDumper
-from obofoundry.utils import ONTOLOGY_DIRECTORY, get_data
+from obofoundry.utils import ONTOLOGY_DIRECTORY, get_data, get_new_data
 
 HERE = Path(__file__).parent.resolve()
 ROOT = HERE.parent
@@ -23,6 +24,16 @@
 ZENODO_PREFIX = "https://zenodo.org/record/"
 DOI_PREFIX = "https://doi.org/"
 CHEMRXIV_DOI_PREFIX = "https://doi.org/10.26434/chemrxiv"
+ALLOWED_SPDX = {
+    "CC0-1.0",  # see https://bioregistry.io/spdx:CC0-1.0
+    "CC-BY-3.0",  # see https://bioregistry.io/spdx:CC-BY-3.0
+    "CC-BY-4.0",  # see https://bioregistry.io/spdx:CC-BY-4.0
+}
+OBO_TO_SPDX = {
+    "CC BY 4.0": "CC-BY-4.0",
+    "CC BY 3.0": "CC-BY-3.0",
+    "CC0": "CC0-1.0",
+}
 
 
 class TestIntegrity(unittest.TestCase):
@@ -253,3 +264,73 @@ def _string_norm(s: str) -> str:
         .replace(".", "")
         .replace("-", "")
     )
+
+
+class TestModernIntegrity(unittest.TestCase):
+    """A test case for data integrity exclusively for new ontologies.
+
+    Specifically, tests implemented in this integrity test are only
+    "going-forwards" and don't need to be retroactively applied. This works
+    since it only looks at ontologies that appear in the /ontologies folder
+    with a markdown file but do not already appear in the published registry
+    build.
+    """
+
+    def setUp(self) -> None:
+        """Set up the test case."""
+        self.ontologies = get_new_data()
+
+    def test_github_references(self):
+        """Test that new ontologies reference the pull request where they were added."""
+        for prefix, data in self.ontologies.items():
+            with self.subTest(prefix=prefix):
+                self.assertIn("pull_request_added", data)
+                self.assertIn("issue_requested", data)
+
+    @lru_cache
+    def _get_github_data(self, prefix: str):
+        data = self.ontologies[prefix]
+        repository = data["repository"]
+        if not repository.startswith("https://github.com"):
+            return None
+        r = repository.removeprefix("https://github.com/").rstrip("/")
+        url = f"https://api.github.com/repos/{r}"
+        res = requests.get(url)
+        res.raise_for_status()
+        return res.json()
+
+    def test_repository_license(self):
+        """Test that the repository has a license that's correct."""
+        for prefix, data in self.ontologies.items():
+            repository = data["repository"]
+            if not repository.startswith("https://github.com"):
+                continue
+            with self.subTest(prefix=prefix):
+                github_data = self._get_github_data(prefix)
+                self.assertIn("license", github_data)
+                self.assertIn("spdx_id", github_data["license"])
+                spdx = github_data["license"]["spdx_id"]
+                self.assertIsNotNone(
+                    spdx, msg="No LICENSE file found in the repository"
+                )
+                self.assertNotEqual(
+                    "NOASSERTION",
+                    spdx,
+                    msg="Either no LICENSE file was found or the LICENSE file does not have a standard format that "
+                    "GitHub can parse. See https://docs.github.com/en/repositories/managing-your-"
+                    "repositorys-settings-and-features/customizing-your-repository/licensing-a-"
+                    "repository#detecting-a-license for information on how GitHub does this.",
+                )
+                self.assertIn(
+                    spdx,
+                    ALLOWED_SPDX,
+                    msg=f"LICENSE file does not follow a standard format for"
+                    f" one of the allowed license types ({ALLOWED_SPDX})",
+                )
+
+                obo_license = data["license"]["label"]
+                self.assertEqual(
+                    spdx,
+                    OBO_TO_SPDX[obo_license],
+                    msg="OBO Foundry license annotation does not match GitHub license",
+                )
diff --git a/tests/test_memberships.py b/tests/test_memberships.py
--- a/tests/test_memberships.py
+++ b/tests/test_memberships.py
@@ -56,7 +56,8 @@ def test_data(self):
         for person in res.members:
             with self.subTest(name=person.name):
                 self.assertFalse(
-                    person.affiliation.ror is None and person.affiliation.wikidata is None,
+                    person.affiliation.ror is None
+                    and person.affiliation.wikidata is None,
                     msg=dedent(
                         f"""\
                         No ROR nor Wikidata identifier was curated for {person.name}.