|
8 | 8 | from rest_framework.test import APIClient |
9 | 9 |
|
10 | 10 | from codecov_auth.models import Account |
11 | | -from shared.django_apps.codecov_auth.models import GithubAppInstallation, Owner |
| 11 | +from shared.django_apps.codecov_auth.models import ( |
| 12 | + GithubAppInstallation, |
| 13 | + Owner, |
| 14 | + Service, |
| 15 | +) |
12 | 16 | from shared.django_apps.codecov_auth.tests.factories import ( |
13 | 17 | AccountFactory, |
14 | 18 | OwnerFactory, |
15 | 19 | PlanFactory, |
16 | 20 | TierFactory, |
17 | 21 | ) |
| 22 | +from shared.django_apps.core.tests.factories import RepositoryFactory |
| 23 | +from shared.django_apps.ta_timeseries.tests.factories import TestrunFactory |
18 | 24 | from shared.plan.constants import PlanName, TierName |
19 | 25 |
|
20 | 26 |
|
@@ -823,3 +829,229 @@ def test_account_unlink_authentication_failure(self): |
823 | 829 | ) |
824 | 830 |
|
825 | 831 | self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) |
| 832 | + |
| 833 | + |
| 834 | +class TestAnalyticsEuViewTests(TestCase): |
| 835 | + databases = ["default", "ta_timeseries"] |
| 836 | + |
| 837 | + def setUp(self): |
| 838 | + self.client = APIClient() |
| 839 | + self.url = reverse("test-analytics-eu") |
| 840 | + |
| 841 | + def _make_authenticated_request(self, data, jwt_payload=None): |
| 842 | + """Helper method to make an authenticated request with JWT payload""" |
| 843 | + with patch( |
| 844 | + "codecov_auth.permissions.get_sentry_jwt_payload" |
| 845 | + ) as mock_get_payload: |
| 846 | + mock_get_payload.return_value = jwt_payload or { |
| 847 | + "g_p": "github", |
| 848 | + "g_o": "test-org", |
| 849 | + } |
| 850 | + return self.client.post( |
| 851 | + self.url, data=json.dumps(data), content_type="application/json" |
| 852 | + ) |
| 853 | + |
| 854 | + def test_test_analytics_eu_empty_integration_names(self): |
| 855 | + """Test that empty integration_names list fails validation""" |
| 856 | + data = {"integration_names": []} |
| 857 | + |
| 858 | + response = self._make_authenticated_request(data=data) |
| 859 | + |
| 860 | + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) |
| 861 | + self.assertIn("integration_names", response.data) |
| 862 | + |
| 863 | + def test_test_analytics_eu_missing_integration_names(self): |
| 864 | + """Test that missing integration_names fails validation""" |
| 865 | + data = {} |
| 866 | + |
| 867 | + response = self._make_authenticated_request(data=data) |
| 868 | + |
| 869 | + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) |
| 870 | + self.assertIn("integration_names", response.data) |
| 871 | + |
| 872 | + @patch("api.sentry.views.log") |
| 873 | + def test_test_analytics_eu_owner_not_found(self, mock_log): |
| 874 | + """Test that non-existent owner is skipped with warning log""" |
| 875 | + data = {"integration_names": ["non-existent-org"]} |
| 876 | + |
| 877 | + response = self._make_authenticated_request(data=data) |
| 878 | + |
| 879 | + self.assertEqual(response.status_code, status.HTTP_200_OK) |
| 880 | + self.assertEqual(response.data["test_runs_per_integration"], {}) |
| 881 | + |
| 882 | + mock_log.warning.assert_called_once() |
| 883 | + warning_call = mock_log.warning.call_args[0][0] |
| 884 | + self.assertIn("non-existent-org", warning_call) |
| 885 | + self.assertIn("not found", warning_call) |
| 886 | + |
| 887 | + def test_test_analytics_eu_owner_without_repositories(self): |
| 888 | + """Test that owner without repositories returns empty dict""" |
| 889 | + OwnerFactory(name="org-no-repos", service=Service.GITHUB) |
| 890 | + |
| 891 | + data = {"integration_names": ["org-no-repos"]} |
| 892 | + |
| 893 | + response = self._make_authenticated_request(data=data) |
| 894 | + |
| 895 | + self.assertEqual(response.status_code, status.HTTP_200_OK) |
| 896 | + self.assertEqual( |
| 897 | + response.data["test_runs_per_integration"], {"org-no-repos": {}} |
| 898 | + ) |
| 899 | + |
| 900 | + @patch("api.sentry.views.log") |
| 901 | + def test_test_analytics_eu_mixed_owners_found_and_not_found(self, mock_log): |
| 902 | + """Test mix of existing and non-existing owners""" |
| 903 | + owner = OwnerFactory(name="org-exists", service=Service.GITHUB) |
| 904 | + repo = RepositoryFactory( |
| 905 | + author=owner, name="test-repo", test_analytics_enabled=True |
| 906 | + ) |
| 907 | + TestrunFactory( |
| 908 | + repo_id=repo.repoid, |
| 909 | + commit_sha="abc123", |
| 910 | + outcome="pass", |
| 911 | + name="test_example", |
| 912 | + ) |
| 913 | + |
| 914 | + data = {"integration_names": ["org-exists", "org-not-exists"]} |
| 915 | + |
| 916 | + response = self._make_authenticated_request(data=data) |
| 917 | + |
| 918 | + self.assertEqual(response.status_code, status.HTTP_200_OK) |
| 919 | + self.assertIn("org-exists", response.data["test_runs_per_integration"]) |
| 920 | + self.assertNotIn("org-not-exists", response.data["test_runs_per_integration"]) |
| 921 | + |
| 922 | + # Verify warning log was called for non-existent owner |
| 923 | + mock_log.warning.assert_called_once() |
| 924 | + warning_call = mock_log.warning.call_args[0][0] |
| 925 | + self.assertIn("org-not-exists", warning_call) |
| 926 | + |
| 927 | + def test_test_analytics_eu_filters_by_test_analytics_enabled(self): |
| 928 | + """Test that only repositories with test_analytics_enabled=True are included""" |
| 929 | + owner = OwnerFactory(name="org-with-repos", service=Service.GITHUB) |
| 930 | + |
| 931 | + repo_enabled = RepositoryFactory( |
| 932 | + author=owner, |
| 933 | + name="repo-enabled", |
| 934 | + test_analytics_enabled=True, |
| 935 | + ) |
| 936 | + TestrunFactory( |
| 937 | + repo_id=repo_enabled.repoid, |
| 938 | + commit_sha="abc123", |
| 939 | + outcome="pass", |
| 940 | + name="test_enabled", |
| 941 | + ) |
| 942 | + |
| 943 | + repo_disabled = RepositoryFactory( |
| 944 | + author=owner, |
| 945 | + name="repo-disabled", |
| 946 | + test_analytics_enabled=False, |
| 947 | + ) |
| 948 | + |
| 949 | + data = {"integration_names": ["org-with-repos"]} |
| 950 | + |
| 951 | + response = self._make_authenticated_request(data=data) |
| 952 | + |
| 953 | + self.assertEqual(response.status_code, status.HTTP_200_OK) |
| 954 | + test_runs_data = response.data["test_runs_per_integration"]["org-with-repos"] |
| 955 | + |
| 956 | + # Only repo-enabled should be in the response |
| 957 | + self.assertIn("repo-enabled", test_runs_data) |
| 958 | + self.assertNotIn("repo-disabled", test_runs_data) |
| 959 | + |
| 960 | + test_runs_list = test_runs_data["repo-enabled"] |
| 961 | + self.assertEqual(len(test_runs_list), 1) |
| 962 | + self.assertEqual(test_runs_list[0]["name"], "test_enabled") |
| 963 | + |
| 964 | + def test_test_analytics_eu_multiple_owners_with_multiple_repos_and_testruns(self): |
| 965 | + """Test complex scenario with 2 owners, different repositories and test runs""" |
| 966 | + owner1 = OwnerFactory(name="org-one", service=Service.GITHUB) |
| 967 | + repo1 = RepositoryFactory( |
| 968 | + author=owner1, |
| 969 | + name="repo-one", |
| 970 | + test_analytics_enabled=True, |
| 971 | + ) |
| 972 | + TestrunFactory( |
| 973 | + repo_id=repo1.repoid, |
| 974 | + commit_sha="commit1", |
| 975 | + outcome="pass", |
| 976 | + name="test_one_first", |
| 977 | + classname="TestClass1", |
| 978 | + ) |
| 979 | + TestrunFactory( |
| 980 | + repo_id=repo1.repoid, |
| 981 | + commit_sha="commit1", |
| 982 | + outcome="failure", |
| 983 | + name="test_one_second", |
| 984 | + classname="TestClass2", |
| 985 | + ) |
| 986 | + |
| 987 | + owner2 = OwnerFactory(name="org-two", service=Service.GITHUB) |
| 988 | + repo2_1 = RepositoryFactory( |
| 989 | + author=owner2, |
| 990 | + name="repo-two-first", |
| 991 | + test_analytics_enabled=True, |
| 992 | + ) |
| 993 | + TestrunFactory( |
| 994 | + repo_id=repo2_1.repoid, |
| 995 | + commit_sha="commit2", |
| 996 | + outcome="pass", |
| 997 | + name="test_two_first", |
| 998 | + classname="TestClassA", |
| 999 | + ) |
| 1000 | + |
| 1001 | + repo2_2 = RepositoryFactory( |
| 1002 | + author=owner2, |
| 1003 | + name="repo-two-second", |
| 1004 | + test_analytics_enabled=True, |
| 1005 | + ) |
| 1006 | + TestrunFactory( |
| 1007 | + repo_id=repo2_2.repoid, |
| 1008 | + commit_sha="commit3", |
| 1009 | + outcome="skip", |
| 1010 | + name="test_two_second", |
| 1011 | + classname="TestClassB", |
| 1012 | + ) |
| 1013 | + |
| 1014 | + data = {"integration_names": ["org-one", "org-two"]} |
| 1015 | + |
| 1016 | + response = self._make_authenticated_request(data=data) |
| 1017 | + |
| 1018 | + self.assertEqual(response.status_code, status.HTTP_200_OK) |
| 1019 | + test_runs_per_integration = response.data["test_runs_per_integration"] |
| 1020 | + |
| 1021 | + # Verify org-one data |
| 1022 | + self.assertIn("org-one", test_runs_per_integration) |
| 1023 | + org_one_data = test_runs_per_integration["org-one"] |
| 1024 | + self.assertIn("repo-one", org_one_data) |
| 1025 | + self.assertEqual(len(org_one_data), 1) |
| 1026 | + |
| 1027 | + repo_one_testruns = org_one_data["repo-one"] |
| 1028 | + self.assertEqual(len(repo_one_testruns), 2) |
| 1029 | + testrun_names = [tr["name"] for tr in repo_one_testruns] |
| 1030 | + self.assertIn("test_one_first", testrun_names) |
| 1031 | + self.assertIn("test_one_second", testrun_names) |
| 1032 | + |
| 1033 | + self.assertIn("org-two", test_runs_per_integration) |
| 1034 | + org_two_data = test_runs_per_integration["org-two"] |
| 1035 | + self.assertIn("repo-two-first", org_two_data) |
| 1036 | + self.assertIn("repo-two-second", org_two_data) |
| 1037 | + self.assertEqual(len(org_two_data), 2) |
| 1038 | + |
| 1039 | + repo_two_first_testruns = org_two_data["repo-two-first"] |
| 1040 | + self.assertEqual(len(repo_two_first_testruns), 1) |
| 1041 | + self.assertEqual(repo_two_first_testruns[0]["name"], "test_two_first") |
| 1042 | + self.assertEqual(repo_two_first_testruns[0]["outcome"], "pass") |
| 1043 | + |
| 1044 | + repo_two_second_testruns = org_two_data["repo-two-second"] |
| 1045 | + self.assertEqual(len(repo_two_second_testruns), 1) |
| 1046 | + self.assertEqual(repo_two_second_testruns[0]["name"], "test_two_second") |
| 1047 | + self.assertEqual(repo_two_second_testruns[0]["outcome"], "skip") |
| 1048 | + |
| 1049 | + def test_test_analytics_eu_authentication_failure(self): |
| 1050 | + """Test that the endpoint requires authentication""" |
| 1051 | + data = {"integration_names": ["test-org"]} |
| 1052 | + |
| 1053 | + response = self.client.post( |
| 1054 | + self.url, data=json.dumps(data), content_type="application/json" |
| 1055 | + ) |
| 1056 | + |
| 1057 | + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) |
0 commit comments