Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 90 additions & 7 deletions ecosystem/validation/test_url.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

"""URL Validations"""

from urllib.parse import urlparse

import pytest

# pylint: disable=missing-function-docstring, missing-class-docstring
Expand All @@ -20,7 +22,7 @@
class TestURLs:
@classmethod
def get_all_urls(cls, member):
"""recursevly search for URLs in the member data"""
"""recursively search for URLs in the member data"""
for key, value in member.to_dict().items():
if isinstance(value, str) and value.startswith("http"):
yield getattr(member, key)
Expand All @@ -29,33 +31,114 @@ def get_all_urls(cls, member):
else:
continue

@staticmethod
def normalize_url(url):

@1ucian0 1ucian0 Jul 9, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you can bake these normalizatoins inside get_all_urls

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thaks for the suggestion .Since get_all_urls() is also used by test_http(), would you prefer inlining the normalization into is_same_or_subpath(), or did you have a different approach in mind for get_all_urls()

"""Normalize URL for comparison."""

if url is None:
return None, None

parsed = urlparse(str(url))

hostname = (parsed.hostname or "").lower()

if hostname.startswith("www."):
hostname = hostname[4:]

path = parsed.path.rstrip("/")

return hostname, path

@staticmethod
def is_same_or_subpath(candidate, reference):
Comment on lines +52 to +53

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it can be implemented as an assertion:

Suggested change
@staticmethod
def is_same_or_subpath(candidate, reference):
def assertSameSubpath(self, candidate, reference):

"""
Check whether candidate duplicates or points inside reference.
"""

candidate_host, candidate_path = TestURLs.normalize_url(candidate)

reference_host, reference_path = TestURLs.normalize_url(reference)

if candidate_host != reference_host:
return False

return candidate_path == reference_path or candidate_path.startswith(
reference_path + "/"
)

def test_http(self, member):
for url in TestURLs.get_all_urls(member):
assert not str(url).startswith("http:"), f"{url} is not HTTPS"

def test_025(self, member):
"""Documentation link has redundant suffix"""

fields = ["url", "documentation", "reference_paper"]

for field in fields:
url = getattr(member, field)

if url is None:
continue
if url.hostname.endswith("readthedocs.io"):
suffixes = ["en/latest/", "en/latest", "en"]

if url.hostname.lower().endswith("readthedocs.io"):
suffixes = [
"en/latest/",
"en/latest",
"en",
]

for suffix in suffixes:
assert not url.path.endswith(
suffix
), f"{url} has redundant suffix: {suffix}"

def test_026(self, member):
"""The /README.md is not documentation"""

documentation_url = getattr(member, "documentation")

if documentation_url is None:
pytest.skip("No member.documentation")
if documentation_url.hostname.endswith("github.com"):
suffixes = ["main/README.md"]

if documentation_url.hostname.lower().endswith("github.com"):
suffixes = [
"main/README.md",
"blob/main/README.md",
"tree/main",
]

for suffix in suffixes:
assert not documentation_url.path.endswith(suffix), (
"The field `member.documentation` can be empty. "
"It does not have to be a link to the `README.md`."
"The field `member.documentation` can be "
"empty. It does not have to be a link to "
"the repository's `README.md` or root."
)

def test_027(self, member):
Comment thread
1ucian0 marked this conversation as resolved.
"""Website should not duplicate GitHub or PyPI URLs"""

website_url = getattr(member, "website")

if website_url is None:
pytest.skip("No member.website")

hostname = website_url.hostname or ""

# Reject GitHub repository URLs as website
repository_url = getattr(member, "url")

if repository_url is not None and repository_url.hostname.lower().endswith(
"github.com"
):
assert not TestURLs.is_same_or_subpath(
website_url,
repository_url,
), (
"The field `member.website` should not "
"duplicate the GitHub repository URL."
)

assert not (
hostname.endswith("pypi.org") or hostname.endswith("pypi.python.org")
), ("The field `member.website` should not " "point to a PyPI project URL.")
9 changes: 9 additions & 0 deletions resources/checks.toml
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,15 @@ checker = "test_url.py::TestURLs::test_026"
category = "SUBMISSION"
importance = "RECOMMENDATION"

[027]
title = "Website should not be GitHub repository or PyPI URL"
description = "The `member.website` field should point to the project's website instead of duplicating the GitHub repository or a PyPI project page."
applies_to = "all"
affects = ["member.website"]
checker = "test_url.py::TestURLs::test_027"
category = "SUBMISSION"
importance = "RECOMMENDATION"

[G05]
title = "Archived GitHub repository should have `archived` support"
applies_to = "GitHub projects"
Expand Down