Skip to content
This repository was archived by the owner on Jun 13, 2025. It is now read-only.

Commit 4703f30

Browse files
committed
add signal for Owner
1 parent 142a003 commit 4703f30

File tree

5 files changed

+161
-19
lines changed

5 files changed

+161
-19
lines changed

codecov_auth/signals.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import json
2+
import logging
23

34
from django.conf import settings
45
from django.db.models.signals import post_save
@@ -7,6 +8,8 @@
78

89
from codecov_auth.models import OrganizationLevelToken, Owner, OwnerProfile
910

11+
log = logging.getLogger(__name__)
12+
1013

1114
@receiver(post_save, sender=Owner)
1215
def create_owner_profile_when_owner_is_created(
@@ -45,3 +48,38 @@ def update_org_token(sender, instance: OrganizationLevelToken, **kwargs):
4548
}
4649
).encode("utf-8"),
4750
)
51+
52+
53+
@receiver(post_save, sender=Owner, dispatch_uid="shelter_sync_owner")
54+
def update_owner(sender, instance: Owner, **kwargs):
55+
"""
56+
Shelter tracks a limited set of Owner fields - only update if those fields have changed.
57+
"""
58+
created = kwargs["created"]
59+
tracked_fields = [
60+
"upload_token_required_for_public_repos",
61+
"username",
62+
"service",
63+
]
64+
if created or any(instance.tracker.has_changed(field) for field in tracked_fields):
65+
pubsub_project_id = settings.SHELTER_PUBSUB_PROJECT_ID
66+
topic_id = settings.SHELTER_PUBSUB_SYNC_REPO_TOPIC_ID
67+
if pubsub_project_id and topic_id:
68+
try:
69+
publisher = _get_pubsub_publisher()
70+
topic_path = publisher.topic_path(pubsub_project_id, topic_id)
71+
publisher.publish(
72+
topic_path,
73+
json.dumps(
74+
{
75+
"type": "owner",
76+
"sync": "one",
77+
"id": instance.ownerid,
78+
}
79+
).encode("utf-8"),
80+
)
81+
except Exception as e:
82+
log.warning(
83+
"Failed to publish message for owner",
84+
extra=dict(owner_id=instance.id, error=e),
85+
)

codecov_auth/tests/test_signals.py

Lines changed: 94 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import os
2+
from unittest import mock
3+
from unittest.mock import call
24

35
import pytest
4-
from django.test import override_settings
6+
from django.test import TestCase, override_settings
7+
from shared.django_apps.codecov_auth.models import Service
58
from shared.django_apps.codecov_auth.tests.factories import (
69
OrganizationLevelTokenFactory,
10+
OwnerFactory,
711
)
812

913

@@ -19,9 +23,94 @@ def test_shelter_org_token_sync(mocker):
1923
publish = mocker.patch("google.cloud.pubsub_v1.PublisherClient.publish")
2024

2125
# this triggers the publish via Django signals
22-
OrganizationLevelTokenFactory(id=91728376)
26+
OrganizationLevelTokenFactory(id=91728376, owner=OwnerFactory(ownerid=111))
2327

24-
publish.assert_called_once_with(
25-
"projects/test-project-id/topics/test-topic-id",
26-
b'{"type": "org_token", "sync": "one", "id": 91728376}',
28+
publish.assert_has_calls(
29+
[
30+
call(
31+
"projects/test-project-id/topics/test-topic-id",
32+
b'{"type": "owner", "sync": "one", "id": 111}',
33+
),
34+
call(
35+
"projects/test-project-id/topics/test-topic-id",
36+
b'{"type": "org_token", "sync": "one", "id": 91728376}',
37+
),
38+
]
2739
)
40+
41+
42+
@override_settings(
43+
SHELTER_PUBSUB_PROJECT_ID="test-project-id",
44+
SHELTER_PUBSUB_SYNC_REPO_TOPIC_ID="test-topic-id",
45+
)
46+
@mock.patch("google.cloud.pubsub_v1.PublisherClient.publish")
47+
class TestCodecovAuthSignals(TestCase):
48+
def setUp(self):
49+
os.environ["PUBSUB_EMULATOR_HOST"] = "localhost"
50+
51+
def test_sync_on_create(self, mock_publish):
52+
OwnerFactory(ownerid=12345)
53+
mock_publish.assert_called_once_with(
54+
"projects/test-project-id/topics/test-topic-id",
55+
b'{"type": "owner", "sync": "one", "id": 12345}',
56+
)
57+
58+
def test_sync_on_update_upload_token_required_for_public_repos(self, mock_publish):
59+
owner = OwnerFactory(ownerid=12345, upload_token_required_for_public_repos=True)
60+
owner.upload_token_required_for_public_repos = False
61+
owner.save()
62+
mock_publish.assert_has_calls(
63+
[
64+
call(
65+
"projects/test-project-id/topics/test-topic-id",
66+
b'{"type": "owner", "sync": "one", "id": 12345}',
67+
),
68+
call(
69+
"projects/test-project-id/topics/test-topic-id",
70+
b'{"type": "owner", "sync": "one", "id": 12345}',
71+
),
72+
]
73+
)
74+
75+
def test_sync_on_update_username(self, mock_publish):
76+
owner = OwnerFactory(ownerid=12345, username="hello")
77+
owner.username = "world"
78+
owner.save()
79+
mock_publish.assert_has_calls(
80+
[
81+
call(
82+
"projects/test-project-id/topics/test-topic-id",
83+
b'{"type": "owner", "sync": "one", "id": 12345}',
84+
),
85+
call(
86+
"projects/test-project-id/topics/test-topic-id",
87+
b'{"type": "owner", "sync": "one", "id": 12345}',
88+
),
89+
]
90+
)
91+
92+
def test_sync_on_update_service(self, mock_publish):
93+
owner = OwnerFactory(ownerid=12345, service=Service.GITHUB.value)
94+
owner.service = Service.BITBUCKET.value
95+
owner.save()
96+
mock_publish.assert_has_calls(
97+
[
98+
call(
99+
"projects/test-project-id/topics/test-topic-id",
100+
b'{"type": "owner", "sync": "one", "id": 12345}',
101+
),
102+
call(
103+
"projects/test-project-id/topics/test-topic-id",
104+
b'{"type": "owner", "sync": "one", "id": 12345}',
105+
),
106+
]
107+
)
108+
109+
def test_no_sync_on_update_other_fields(self, mock_publish):
110+
owner = OwnerFactory(ownerid=12345, name="hello")
111+
owner.name = "world"
112+
owner.save()
113+
mock_publish.assert_called_once_with(
114+
"projects/test-project-id/topics/test-topic-id",
115+
b'{"type": "owner", "sync": "one", "id": 12345}',
116+
)

core/tests/test_signals.py

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import pytest
55
from django.test import override_settings
6+
from shared.django_apps.codecov_auth.tests.factories import OwnerFactory
67
from shared.django_apps.core.tests.factories import CommitFactory, RepositoryFactory
78

89

@@ -18,22 +19,30 @@ def test_shelter_repo_sync(mocker):
1819
publish = mocker.patch("google.cloud.pubsub_v1.PublisherClient.publish")
1920

2021
# this triggers the publish via Django signals
21-
repo = RepositoryFactory(repoid=91728376)
22+
repo = RepositoryFactory(repoid=91728376, author=OwnerFactory(ownerid=555))
2223

2324
# triggers publish on create
24-
publish.assert_called_once_with(
25-
"projects/test-project-id/topics/test-topic-id",
26-
b'{"type": "repo", "sync": "one", "id": 91728376}',
25+
publish.assert_has_calls(
26+
[
27+
call(
28+
"projects/test-project-id/topics/test-topic-id",
29+
b'{"type": "owner", "sync": "one", "id": 555}',
30+
),
31+
call(
32+
"projects/test-project-id/topics/test-topic-id",
33+
b'{"type": "repo", "sync": "one", "id": 91728376}',
34+
),
35+
]
2736
)
2837

2938
repo.upload_token = "b69cf44c-89d8-48c2-80c9-5508610d3ced"
3039
repo.save()
3140

3241
publish_calls = publish.call_args_list
33-
assert len(publish_calls) == 2
42+
assert len(publish_calls) == 3
3443

3544
# triggers publish on update
36-
assert publish_calls[1] == call(
45+
assert publish_calls[2] == call(
3746
"projects/test-project-id/topics/test-topic-id",
3847
b'{"type": "repo", "sync": "one", "id": 91728376}',
3948
)
@@ -43,7 +52,7 @@ def test_shelter_repo_sync(mocker):
4352

4453
publish_calls = publish.call_args_list
4554
# does not trigger another publish
46-
assert len(publish_calls) == 2
55+
assert len(publish_calls) == 3
4756

4857

4958
@override_settings(
@@ -57,15 +66,21 @@ def test_shelter_commit_sync(mocker):
5766
publish = mocker.patch("google.cloud.pubsub_v1.PublisherClient.publish")
5867

5968
# this triggers the publish via Django signals - has to have this format
60-
commit = CommitFactory(id=167829367, branch="random:branch")
69+
owner = OwnerFactory(ownerid=555)
70+
commit = CommitFactory(
71+
id=167829367,
72+
branch="random:branch",
73+
author=owner,
74+
repository=RepositoryFactory(author=owner),
75+
)
6176

6277
publish_calls = publish.call_args_list
63-
# Twice cause there's a signal triggered when the commit factory creates a Repository
78+
# 3x cause there's a signal triggered when the commit factory requires a Repository and Owner
6479
# which can't be null
65-
assert len(publish_calls) == 2
80+
assert len(publish_calls) == 3
6681

6782
# triggers publish on update
68-
assert publish_calls[1] == call(
83+
assert publish_calls[2] == call(
6984
"projects/test-project-id/topics/test-topic-id",
7085
b'{"type": "commit", "sync": "one", "id": 167829367}',
7186
)
@@ -75,4 +90,4 @@ def test_shelter_commit_sync(mocker):
7590

7691
publish_calls = publish.call_args_list
7792
# does not trigger another publish since unchanged length
78-
assert len(publish_calls) == 2
93+
assert len(publish_calls) == 3

requirements.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ factory-boy
2020
fakeredis
2121
freezegun
2222
https://github.com/codecov/opentelem-python/archive/refs/tags/v0.0.4a1.tar.gz#egg=codecovopentelem
23-
https://github.com/codecov/shared/archive/067b2e4ec72cdf1b3213306ea8aaaccbb374aecc.tar.gz#egg=shared
23+
https://github.com/codecov/shared/archive/b08713571df87aa2664e1b53857b27bfccff32a7.tar.gz#egg=shared
2424
google-cloud-pubsub
2525
gunicorn>=22.0.0
2626
https://github.com/photocrowd/django-cursor-pagination/archive/f560902696b0c8509e4d95c10ba0d62700181d84.tar.gz

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ sentry-sdk[celery]==2.13.0
419419
# shared
420420
setproctitle==1.1.10
421421
# via -r requirements.in
422-
shared @ https://github.com/codecov/shared/archive/067b2e4ec72cdf1b3213306ea8aaaccbb374aecc.tar.gz
422+
shared @ https://github.com/codecov/shared/archive/b08713571df87aa2664e1b53857b27bfccff32a7.tar.gz
423423
# via -r requirements.in
424424
simplejson==3.17.2
425425
# via -r requirements.in

0 commit comments

Comments
 (0)