Coverage for tests/publisher/snaps/test_builds.py: 100%

46 statements  

« prev     ^ index     » next       coverage.py v7.10.5, created at 2025-08-26 22:06 +0000

1import unittest 

2from unittest.mock import patch 

3 

4from webapp.publisher.snaps.builds import map_build_and_upload_states 

5from webapp.publisher.snaps.build_views import ( 

6 extract_github_repository, 

7 get_builds, 

8) 

9 

10 

11class TestBuildStateMapper(unittest.TestCase): 

12 def test_build_state_mappings(self): 

13 combinations = [ 

14 ("Needs building", "Unscheduled", "building_soon"), 

15 ("Needs building", "Pending", "building_soon"), 

16 ("Needs building", "Failed to upload", "building_soon"), 

17 ( 

18 "Needs building", 

19 "Failed to release to channels", 

20 "building_soon", 

21 ), 

22 ("Needs building", "Uploaded", "building_soon"), 

23 ("Successfully built", "Unscheduled", "wont_release"), 

24 ("Successfully built", "Pending", "releasing_soon"), 

25 ("Successfully built", "Failed to upload", "release_failed"), 

26 ( 

27 "Successfully built", 

28 "Failed to release to channels", 

29 "release_failed", 

30 ), 

31 ("Successfully built", "Uploaded", "released"), 

32 ("Currently building", "Unscheduled", "in_progress"), 

33 ("Currently building", "Pending", "in_progress"), 

34 ("Currently building", "Failed to upload", "in_progress"), 

35 ( 

36 "Currently building", 

37 "Failed to release to channels", 

38 "in_progress", 

39 ), 

40 ("Currently building", "Uploaded", "in_progress"), 

41 ("Failed to build", "Unscheduled", "failed_to_build"), 

42 ("Failed to build", "Pending", "failed_to_build"), 

43 ("Failed to build", "Failed to upload", "failed_to_build"), 

44 ( 

45 "Failed to build", 

46 "Failed to release to channels", 

47 "failed_to_build", 

48 ), 

49 ("Failed to build", "Uploaded", "failed_to_build"), 

50 ("Dependency wait", "Unscheduled", "failed_to_build"), 

51 ("Dependency wait", "Pending", "failed_to_build"), 

52 ("Dependency wait", "Failed to upload", "failed_to_build"), 

53 ( 

54 "Dependency wait", 

55 "Failed to release to channels", 

56 "failed_to_build", 

57 ), 

58 ("Dependency wait", "Uploaded", "failed_to_build"), 

59 ("Chroot problem", "Unscheduled", "failed_to_build"), 

60 ("Chroot problem", "Pending", "failed_to_build"), 

61 ("Chroot problem", "Failed to upload", "failed_to_build"), 

62 ( 

63 "Chroot problem", 

64 "Failed to release to channels", 

65 "failed_to_build", 

66 ), 

67 ("Chroot problem", "Uploaded", "failed_to_build"), 

68 ("Build for superseded Source", "Unscheduled", "failed_to_build"), 

69 ("Build for superseded Source", "Pending", "failed_to_build"), 

70 ( 

71 "Build for superseded Source", 

72 "Failed to upload", 

73 "failed_to_build", 

74 ), 

75 ( 

76 "Build for superseded Source", 

77 "Failed to release to channels", 

78 "failed_to_build", 

79 ), 

80 ("Build for superseded Source", "Uploaded", "failed_to_build"), 

81 ("Failed to upload", "Unscheduled", "failed_to_build"), 

82 ("Failed to upload", "Pending", "failed_to_build"), 

83 ("Failed to upload", "Failed to upload", "failed_to_build"), 

84 ( 

85 "Failed to upload", 

86 "Failed to release to channels", 

87 "failed_to_build", 

88 ), 

89 ("Failed to upload", "Uploaded", "failed_to_build"), 

90 ("Cancelling build", "Unscheduled", "cancelled"), 

91 ("Cancelling build", "Pending", "cancelled"), 

92 ("Cancelling build", "Cancelling build", "cancelled"), 

93 ( 

94 "Cancelling build", 

95 "Failed to release to channels", 

96 "cancelled", 

97 ), 

98 ("Cancelling build", "Uploaded", "cancelled"), 

99 ("Cancelled build", "Unscheduled", "cancelled"), 

100 ("Cancelled build", "Pending", "cancelled"), 

101 ("Cancelled build", "Cancelled build", "cancelled"), 

102 ( 

103 "Cancelled build", 

104 "Failed to release to channels", 

105 "cancelled", 

106 ), 

107 ("Cancelled build", "Uploaded", "cancelled"), 

108 ("Failed to upload", "Unscheduled", "failed_to_build"), 

109 ("Failed to upload", "Pending", "failed_to_build"), 

110 ("Failed to upload", "Failed to upload", "failed_to_build"), 

111 ( 

112 "Failed to upload", 

113 "Failed to release to channels", 

114 "failed_to_build", 

115 ), 

116 ("Failed to upload", "Uploaded", "failed_to_build"), 

117 ] 

118 

119 for build_state, upload_state, expected in combinations: 

120 result = map_build_and_upload_states(build_state, upload_state) 

121 self.assertEqual(result, expected) 

122 

123 

124class TestGetBuilds(unittest.TestCase): 

125 @patch("webapp.publisher.snaps.build_views.launchpad") 

126 def test_get_builds_includes_github_repository(self, mock_launchpad): 

127 """Test that get_builds includes GitHub repository information""" 

128 # Mock Launchpad snap data with GitHub repository URL 

129 lp_snap = { 

130 "store_name": "test-snap", 

131 "git_repository_url": "https://github.com/owner/repo", 

132 } 

133 

134 # Mock build data from Launchpad 

135 mock_builds = [ 

136 { 

137 "self_link": ( 

138 "https://api.launchpad.net/devel/~owner/" 

139 "+snap/test-snap/+build/123" 

140 ), 

141 "arch_tag": "amd64", 

142 "datebuilt": "2023-01-01T12:00:00Z", 

143 "duration": "00:05:30", 

144 "build_log_url": ( 

145 "https://launchpad.net/~owner/+snap/test-snap/" 

146 "+build/123/+files/buildlog.txt" 

147 ), 

148 "revision_id": "abcdef1234567890abcdef1234567890abcdef12", 

149 "buildstate": "Successfully built", 

150 "store_upload_status": "Uploaded", 

151 "title": "Test build", 

152 } 

153 ] 

154 

155 mock_launchpad.get_snap_builds.return_value = mock_builds 

156 

157 # Call get_builds 

158 result = get_builds(lp_snap, slice(0, 10)) 

159 

160 # Verify the result includes GitHub repository information 

161 self.assertEqual(result["total_builds"], 1) 

162 self.assertEqual(len(result["snap_builds"]), 1) 

163 

164 build = result["snap_builds"][0] 

165 self.assertEqual(build["id"], "123") 

166 self.assertEqual(build["arch_tag"], "amd64") 

167 self.assertEqual( 

168 build["revision_id"], "abcdef1234567890abcdef1234567890abcdef12" 

169 ) 

170 self.assertEqual(build["github_repository"], "owner/repo") 

171 self.assertEqual(build["status"], "released") 

172 

173 @patch("webapp.publisher.snaps.build_views.launchpad") 

174 def test_get_builds_without_github_repository(self, mock_launchpad): 

175 """Test that get_builds handles snaps without GitHub repository""" 

176 # Mock Launchpad snap data without GitHub repository URL 

177 lp_snap = {"store_name": "test-snap"} 

178 

179 # Mock build data from Launchpad 

180 mock_builds = [ 

181 { 

182 "self_link": ( 

183 "https://api.launchpad.net/devel/~owner/" 

184 "+snap/test-snap/+build/123" 

185 ), 

186 "arch_tag": "amd64", 

187 "datebuilt": "2023-01-01T12:00:00Z", 

188 "duration": "00:05:30", 

189 "build_log_url": ( 

190 "https://launchpad.net/~owner/+snap/test-snap/" 

191 "+build/123/+files/buildlog.txt" 

192 ), 

193 "revision_id": "abcdef1234567890abcdef1234567890abcdef12", 

194 "buildstate": "Successfully built", 

195 "store_upload_status": "Uploaded", 

196 "title": "Test build", 

197 } 

198 ] 

199 

200 mock_launchpad.get_snap_builds.return_value = mock_builds 

201 

202 # Call get_builds 

203 result = get_builds(lp_snap, slice(0, 10)) 

204 

205 # Verify the result has None for GitHub repository 

206 build = result["snap_builds"][0] 

207 self.assertIsNone(build["github_repository"]) 

208 

209 

210class TestExtractGithubRepository(unittest.TestCase): 

211 """Test the extract_github_repository helper function.""" 

212 

213 def test_extract_valid_github_url(self): 

214 """Test extracting owner/repo from valid GitHub URLs.""" 

215 test_cases = [ 

216 ("https://github.com/owner/repo", "owner/repo"), 

217 ("https://github.com/owner/repo.git", "owner/repo"), 

218 ("https://github.com/owner/repo/", "owner/repo"), 

219 ("https://github.com/owner/repo.git/", "owner/repo"), 

220 ("http://github.com/owner/repo", "owner/repo"), 

221 ] 

222 

223 for url, expected in test_cases: 

224 with self.subTest(url=url): 

225 result = extract_github_repository(url) 

226 self.assertEqual(result, expected) 

227 

228 def test_extract_invalid_urls(self): 

229 """Test that invalid URLs return None.""" 

230 test_cases = [ 

231 None, 

232 "", 

233 "https://gitlab.com/owner/repo", 

234 "https://bitbucket.org/owner/repo", 

235 "not-a-url", 

236 "https://github.com/", 

237 "https://github.com/owner", 

238 ] 

239 

240 for url in test_cases: 

241 with self.subTest(url=url): 

242 result = extract_github_repository(url) 

243 self.assertIsNone(result)