Skip to content
Draft
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
43 changes: 29 additions & 14 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from logging import basicConfig, INFO
from readme_level import ReadmeLevel

DEFAULT_PROGRESS_BAR_CHAR_LENGTH: int = 30

# set default config for application logging
basicConfig(
level=INFO,
Expand All @@ -13,29 +15,42 @@
)


def _get_progress_bar_length() -> int:
"""Gets and validates the progress bar length from environment variables."""
progress_bar_length: str | None = getenv("INPUT_PROGRESS_BAR_CHAR_LENGTH")

if not progress_bar_length:
return DEFAULT_PROGRESS_BAR_CHAR_LENGTH

try:
value: int = int(progress_bar_length)
except ValueError:
return DEFAULT_PROGRESS_BAR_CHAR_LENGTH

return value if value > 0 else DEFAULT_PROGRESS_BAR_CHAR_LENGTH


def _env_is_truthy(var_name: str) -> bool:
"""Converts common environment variable values to a boolean."""
return getenv(var_name, "").lower() in {"1", "true", "yes", "on"}


def draw_progress_bar(current_progress: float | int) -> str:
"""Draws the progress bar"""
progress_bar_length: int = int(getenv("INPUT_PROGRESS_BAR_CHAR_LENGTH"))
progress_bar_length: int = _get_progress_bar_length()
current_progress = max(0.0, min(100.0, float(current_progress)))

progress_bar_content = {
"empty_bar": getenv("INPUT_EMPTY_BAR"),
"filled_bar": getenv("INPUT_FILLED_BAR")
}

progress_bar: str = ""
filled_progress: int = round(
progress_bar_length * (current_progress / 100), 0)

for index in range(progress_bar_length):

# because the index starts at 0 we reduce filled_progress
# we should define our own index
if index <= filled_progress - 1:
progress_bar += progress_bar_content["filled_bar"]

if index > filled_progress - 1:
progress_bar += progress_bar_content["empty_bar"]

progress_bar: str = (
progress_bar_content["filled_bar"] * int(filled_progress) +
progress_bar_content["empty_bar"] * (progress_bar_length - int(filled_progress))
)
return progress_bar


Expand All @@ -58,7 +73,7 @@ def generate_content(readme_instance: ReadmeLevel, start_section: str, end_secti
f"{ getenv('INPUT_CARD_TITLE') if getenv('INPUT_CARD_TITLE') else '' } \n"
f"<pre>level: { user_level } \
{ draw_progress_bar(to_next_lvl) } {round(to_next_lvl, 2)}%</pre>\n"
f"{ ep_information if getenv('INPUT_SHOW_EP_INFO') else '' }"
f"{ ep_information if _env_is_truthy('INPUT_SHOW_EP_INFO') else '' }"
f"{end_section}")


Expand Down
51 changes: 32 additions & 19 deletions readme_level.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"""Module that contains all the logic about the levelsystem."""
from os import getenv
from datetime import datetime
from logging import exception, info
from logging import exception, info, error
from requests import post
from requests.exceptions import RequestException
from graphql_query import REQUEST_QUERY
from readme_data import ReadmeLevelData

Expand All @@ -22,30 +23,39 @@ def __init__(self) -> None:
def fetch_user_data(self) -> dict[str, int] | None:
"""Fetches the user data from github api"""

if not getenv("INPUT_GITHUB_TOKEN"):
exception("an error with the github token occurred")
github_token: str | None = getenv("INPUT_GITHUB_TOKEN")
if not github_token:
error("missing github token")
return None

auth_header = {"Authorization": "Bearer " +
getenv("INPUT_GITHUB_TOKEN")}
response = post("https://api.github.com/graphql",
json={"query": REQUEST_QUERY}, headers=auth_header, timeout=2)
auth_header = {"Authorization": "Bearer " + github_token}

if response.status_code == 200:
info("request to github api was successfull")
try:
response = post("https://api.github.com/graphql",
json={"query": REQUEST_QUERY}, headers=auth_header, timeout=2)
except RequestException:
exception("request to github api failed")
return None

response_data = response.json()
if response.status_code != 200:
error("request to github api failed with status code %s", response.status_code)
return None

current_year = datetime.now().year
total_contribution = []
info("request to github api was successful")

response_data = response.json()

current_year = datetime.now().year
total_contribution = []

try:
while current_year >= 2015:
contribution_count = (response_data["data"]["user"]
["_" + str(current_year)]["contributionCalendar"]["totalContributions"])

total_contribution.append(contribution_count)
current_year -= 1


user_data = {}

user_data["totalContributions"] = sum(total_contribution)
Expand All @@ -54,18 +64,21 @@ def fetch_user_data(self) -> dict[str, int] | None:
["followers"]["totalCount"])

user_data["totalRepositories"] = (response_data["data"]["user"]
["repositories"]["totalCount"])

return user_data
["repositories"]["totalCount"])
except (KeyError, TypeError):
exception("github api response is malformed")
return None

exception("request to github api failed")
return None
return user_data

def calc_current_ep(self) -> int:
"""Calculates the current user experience points"""

# get user stats
user_stats: dict[str, int] = self.fetch_user_data()
user_stats: dict[str, int] | None = self.fetch_user_data()
if user_stats is None:
self.current_ep = 0
return self.current_ep

# calc the current experience points
self.current_ep = (
Expand Down
21 changes: 20 additions & 1 deletion tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,28 @@ def test_generate_content(self) -> None:
mock_readme_instance, start_section, end_section)

# we check only if return value is from type str because the spaces makes
# it realy difficult to check for isEqual
# it really difficult to check for isEqual
self.assertIsInstance(replace_str, str)

@patch.dict(os.environ, {'INPUT_SHOW_EP_INFO': 'false'}, clear=True)
def test_generate_content_without_ep_info(self) -> None:
"""Tests generated content without ep information."""
mock_readme_instance = MagicMock()
mock_readme_instance.get_current_level.return_value = {
"current_level": "10", "percentage_level": 20}
mock_readme_instance.get_contribution_ep = 20
mock_readme_instance.get_follower_ep = 20
mock_readme_instance.get_project_ep = 5

with patch('main.draw_progress_bar', return_value="███"):
replace_str: str = generate_content(
mock_readme_instance,
"<!--README_LEVEL_UP:START-->",
"<!--README_LEVEL_UP:END-->"
)

self.assertNotIn("experience points", replace_str)


if __name__ == '__main__':
unittest.main()
33 changes: 32 additions & 1 deletion tests/test_readme_level.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
Unit Tests for readme_level.py
"""

import os
import unittest
from unittest.mock import patch
from unittest.mock import patch, MagicMock
from readme_level import ReadmeLevel

class TestLevelSystem(unittest.TestCase):
Expand Down Expand Up @@ -37,6 +38,36 @@ def test_calc_current_ep(self, mock_fetch_user_data):
readme_instance.calc_current_ep()
self.assertEqual(readme_instance.current_ep, 2725)

@patch('readme_level.ReadmeLevel.fetch_user_data')
def test_calc_current_ep_without_user_data(self, mock_fetch_user_data):
"""Tests ep calc with missing user data."""
mock_fetch_user_data.return_value = None

readme_instance: ReadmeLevel = ReadmeLevel()
readme_instance.calc_current_ep()
self.assertEqual(readme_instance.current_ep, 0)

@patch.dict(os.environ, {}, clear=True)
@patch('readme_level.post')
def test_fetch_user_data_without_token(self, mock_post):
"""Tests fetch_user_data returns None if token is missing."""
readme_instance: ReadmeLevel = ReadmeLevel()
user_data = readme_instance.fetch_user_data()

self.assertIsNone(user_data)
mock_post.assert_not_called()

@patch.dict(os.environ, {"INPUT_GITHUB_TOKEN": "test-token"}, clear=True)
@patch('readme_level.post')
def test_fetch_user_data_unsuccessful_response(self, mock_post):
"""Tests fetch_user_data handles non-200 responses."""
mock_post.return_value = MagicMock(status_code=500)

readme_instance: ReadmeLevel = ReadmeLevel()
user_data = readme_instance.fetch_user_data()

self.assertIsNone(user_data)


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