Coverage for tests/api/test_github.py: 100%

75 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-20 22:09 +0000

1from os import getenv 

2from unittest import TestCase 

3from unittest.mock import MagicMock 

4 

5from vcr_unittest import VCRTestCase 

6from webapp.api.github import GitHub, repository_is_public 

7from werkzeug.exceptions import Unauthorized 

8 

9 

10class GitHubTest(VCRTestCase): 

11 def _get_vcr_kwargs(self): 

12 """ 

13 This removes the authorization header 

14 from VCR so we don't record auth parameters 

15 """ 

16 return {"filter_headers": ["Authorization"]} 

17 

18 def setUp(self): 

19 self.client = GitHub(getenv("TESTS_GITHUB_USER_TOKEN", "secret")) 

20 return super(GitHubTest, self).setUp() 

21 

22 def test_get_user(self): 

23 user = self.client.get_user() 

24 self.assertIn("login", user) 

25 self.assertIn("name", user) 

26 self.assertIn("avatarUrl", user) 

27 

28 # Test Unauthorized exception when using bad credentials 

29 self.client.access_token = "bad-token" 

30 self.assertRaises(Unauthorized, self.client.get_user) 

31 

32 def test_get_user_repositories(self): 

33 repos = self.client.get_user_repositories() 

34 [self.assertIn("name", repo) for repo in repos] 

35 

36 # Test Unauthorized exception when using bad credentials 

37 self.client.access_token = "bad-token" 

38 self.assertRaises(Unauthorized, self.client.get_user_repositories) 

39 

40 def test_get_org_repositories(self): 

41 repos = self.client.get_org_repositories("canonical-web-and-design") 

42 [self.assertIn("name", repo) for repo in repos] 

43 [self.assertIn("nameWithOwner", repo) for repo in repos] 

44 [self.assertIn("owner", repo) for repo in repos] 

45 

46 # Test Unauthorized exception when using bad credentials 

47 self.client.access_token = "bad-token" 

48 self.assertRaises(Unauthorized, self.client.get_user_repositories) 

49 

50 def test_get_orgs(self): 

51 orgs = self.client.get_orgs() 

52 [self.assertIn("name", org) for org in orgs] 

53 [self.assertIn("login", org) for org in orgs] 

54 

55 def test_check_permissions_over_repo(self): 

56 # The user is the owner of the repo 

57 case1 = self.client.check_permissions_over_repo( 

58 "build-staging-snapcraft-io", "test1" 

59 ) 

60 self.assertEqual(True, case1) 

61 

62 # The user doesn't have permissions for this repo 

63 case2 = self.client.check_permissions_over_repo( 

64 "canonical-web-and-design", "snapcraft.io" 

65 ) 

66 self.assertEqual(False, case2) 

67 

68 def test_get_snapcraft_yaml_location(self): 

69 # /snapcraft.yaml is present 

70 case1 = self.client.get_snapcraft_yaml_location( 

71 "build-staging-snapcraft-io", "test1" 

72 ) 

73 self.assertEqual("snapcraft.yaml", case1) 

74 

75 # /.snapcraft.yaml is present 

76 case2 = self.client.get_snapcraft_yaml_location( 

77 "build-staging-snapcraft-io", "test2" 

78 ) 

79 self.assertEqual(".snapcraft.yaml", case2) 

80 

81 # /snap/snapcraft.yaml is present 

82 case3 = self.client.get_snapcraft_yaml_location( 

83 "build-staging-snapcraft-io", "test3" 

84 ) 

85 self.assertEqual("snap/snapcraft.yaml", case3) 

86 

87 # /build-aux/snap/snapcraft.yaml is present 

88 case4 = self.client.get_snapcraft_yaml_location( 

89 "build-staging-snapcraft-io", "test4" 

90 ) 

91 self.assertEqual("build-aux/snap/snapcraft.yaml", case4) 

92 

93 # The repo doesn't contain a valid yaml file 

94 case5 = self.client.get_snapcraft_yaml_location( 

95 "build-staging-snapcraft-io", "test5" 

96 ) 

97 self.assertEqual(False, case5) 

98 

99 def test_get_snapcraft_yaml_data(self): 

100 case1 = self.client.get_snapcraft_yaml_data( 

101 "build-staging-snapcraft-io", "test1" 

102 ) 

103 self.assertEqual("test1", case1.get("name")) 

104 

105 case2 = self.client.get_snapcraft_yaml_data( 

106 "build-staging-snapcraft-io", "test5" 

107 ) 

108 self.assertEqual(None, case2.get("name")) 

109 

110 

111class RepositoryIsPublicTest(TestCase): 

112 """A recipe can outlive the repository it names.""" 

113 

114 def _session(self, status_code=None, error=None): 

115 session = MagicMock() 

116 if error: 

117 session.head.side_effect = error 

118 else: 

119 session.head.return_value = MagicMock(status_code=status_code) 

120 return session 

121 

122 def test_public_repository(self): 

123 self.assertTrue( 

124 repository_is_public("snapcrafters/mumble", self._session(200)) 

125 ) 

126 

127 def test_missing_repository(self): 

128 self.assertFalse(repository_is_public("gone/repo", self._session(404))) 

129 

130 def test_no_repository(self): 

131 session = self._session(200) 

132 self.assertTrue(repository_is_public(None, session)) 

133 # Nothing to check, so nothing requested. 

134 session.head.assert_not_called() 

135 

136 def test_fails_open_on_error(self): 

137 # A timeout must never erase provenance we hold. 

138 self.assertTrue( 

139 repository_is_public( 

140 "snapcrafters/mumble", 

141 self._session(error=Exception("read timed out")), 

142 ) 

143 ) 

144 

145 def test_fails_open_on_rate_limit(self): 

146 self.assertTrue( 

147 repository_is_public("snapcrafters/mumble", self._session(429)) 

148 )