Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add functions to get/set apps to commits #469

Merged
merged 2 commits into from
May 28, 2024
Merged
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
48 changes: 48 additions & 0 deletions services/github.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import logging
from typing import Optional

from redis import RedisError
from shared.github import InvalidInstallationError
from shared.github import get_github_integration_token as _get_github_integration_token

from database.models.core import Commit
from helpers.cache import cache
from helpers.exceptions import RepositoryWithoutValidBotError
from services.redis import get_redis_connection

log = logging.getLogger(__name__)

Expand All @@ -24,3 +27,48 @@ def get_github_integration_token(
except InvalidInstallationError:
log.warning("Failed to get installation token")
raise RepositoryWithoutValidBotError()


COMMIT_GHAPP_KEY_NAME = lambda commit_id: f"app_to_use_for_commit_{commit_id}"
GHAPP_KEY_EXPIRY_SECONDS = 60 * 60 * 2 # 2h


def set_github_app_for_commit(
installation_id: str | int | None, commit: Commit
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, installation_id has a lot of different types. In what circumstances should we be expecting a str vs int vs None type? Should this be documented?

) -> bool:
"""Sets a GithubAppInstallation.id in Redis as the installation to use for a commit.
Keys live in redis for GHAPP_KEY_EXPIRY_SECONDS before being expired.

Args:
installation_id (str | int | None) - the ID to save.
None -- there was actually no installation ID. Do nothing.
int -- value comes from the Database
str -- value comes from Redis (i.e. the installation was already cached)
commit (Commit) - the commit to attach installation_id to
"""
if installation_id is None:
return False
redis = get_redis_connection()
try:
redis.set(
COMMIT_GHAPP_KEY_NAME(commit.id),
str(installation_id),
ex=GHAPP_KEY_EXPIRY_SECONDS,
)
return True
except RedisError:
log.exception(
"Failed to set app for commit", extra=dict(commit=commit.commitid)
)
return False


def get_github_app_for_commit(commit: Commit) -> str | None:
redis = get_redis_connection()
try:
return redis.get(COMMIT_GHAPP_KEY_NAME(commit.id))
except RedisError:
log.exception(
"Failed to get app for commit", extra=dict(commit=commit.commitid)
)
return None
71 changes: 71 additions & 0 deletions services/tests/test_github.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from unittest.mock import MagicMock

import pytest
from redis import RedisError

from database.models.core import GithubAppInstallation, Owner
from database.tests.factories.core import CommitFactory
from services.github import get_github_app_for_commit, set_github_app_for_commit


class TestGetSetGithubAppsToCommits(object):
def _get_commit(self, dbsession):
commit = CommitFactory(repository__owner__service="github")
dbsession.add(commit)
dbsession.flush()
return commit

def _get_app(self, owner: Owner, dbsession):
app = GithubAppInstallation(
owner=owner, installation_id=1250, app_id=250, pem_path="some_path"
)
dbsession.add(app)
dbsession.flush()
return app

@pytest.fixture
def mock_redis(self, mocker):
fake_redis = MagicMock(name="fake_redis")
mock_conn = mocker.patch("services.github.get_redis_connection")
mock_conn.return_value = fake_redis
return fake_redis

def test_set_app_for_commit_no_app(self, mock_redis, dbsession):
commit = self._get_commit(dbsession)
assert set_github_app_for_commit(None, commit) == False
mock_redis.set.assert_not_called()

def test_set_app_for_commit_redis_success(self, mock_redis, dbsession):
commit = self._get_commit(dbsession)
app = self._get_app(commit.repository.owner, dbsession)
assert set_github_app_for_commit(app.id, commit) == True
mock_redis.set.assert_called_with(
f"app_to_use_for_commit_{commit.id}", str(app.id), ex=(60 * 60 * 2)
)

def test_set_app_for_commit_redis_error(self, mock_redis, dbsession):
commit = self._get_commit(dbsession)
mock_redis.set.side_effect = RedisError
assert set_github_app_for_commit("1000", commit) == False
mock_redis.set.assert_called_with(
f"app_to_use_for_commit_{commit.id}", "1000", ex=(60 * 60 * 2)
)

def test_get_app_for_commit(self, mock_redis):
redis_keys = {
"app_to_use_for_commit_12": "1200",
"app_to_use_for_commit_10": "1000",
}
fake_commit_12 = MagicMock(name="fake_commit", **{"id": 12})
fake_commit_10 = MagicMock(name="fake_commit", **{"id": 10})
fake_commit_50 = MagicMock(name="fake_commit", **{"id": 50})
mock_redis.get.side_effect = lambda key: redis_keys.get(key)
assert get_github_app_for_commit(fake_commit_12) == "1200"
assert get_github_app_for_commit(fake_commit_10) == "1000"
assert get_github_app_for_commit(fake_commit_50) == None

def test_get_app_for_commit_error(self, mock_redis):
mock_redis.get.side_effect = RedisError
fake_commit_12 = MagicMock(name="fake_commit", **{"id": 12})
assert get_github_app_for_commit(fake_commit_12) == None
mock_redis.get.assert_called_with("app_to_use_for_commit_12")
Loading