1# Copyright 2020 Google Inc. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14# 15################################################################################ 16"""Unit tests for Cloud Function update builds status.""" 17import os 18import sys 19import unittest 20from unittest import mock 21from unittest.mock import MagicMock 22 23from google.cloud import ndb 24 25sys.path.append(os.path.dirname(__file__)) 26# pylint: disable=wrong-import-position 27 28from datastore_entities import BuildsHistory 29from datastore_entities import LastSuccessfulBuild 30import test_utils 31import update_build_status 32 33# pylint: disable=no-member 34 35 36# pylint: disable=too-few-public-methods 37class MockGetBuild: 38 """Spoofing get_builds function.""" 39 40 def __init__(self, builds): 41 self.builds = builds 42 43 def get_build(self, cloudbuild, image_project, build_id): 44 """Mimic build object retrieval.""" 45 del cloudbuild, image_project 46 for build in self.builds: 47 if build['build_id'] == build_id: 48 return build 49 50 return None 51 52 53@mock.patch('google.auth.default', return_value=['temp', 'temp']) 54@mock.patch('update_build_status.build', return_value='cloudbuild') 55@mock.patch('update_build_status.upload_log') 56class TestGetBuildHistory(unittest.TestCase): 57 """Unit tests for get_build_history.""" 58 59 def test_get_build_history(self, mock_upload_log, mock_cloud_build, 60 mock_google_auth): 61 """Test for get_build_steps.""" 62 del mock_cloud_build, mock_google_auth 63 mock_upload_log.return_value = True 64 builds = [{'build_id': '1', 'finishTime': 'test_time', 'status': 'SUCCESS'}] 65 mock_get_build = MockGetBuild(builds) 66 update_build_status.get_build = mock_get_build.get_build 67 68 expected_projects = { 69 'history': [{ 70 'build_id': '1', 71 'finish_time': 'test_time', 72 'success': True 73 }], 74 'last_successful_build': { 75 'build_id': '1', 76 'finish_time': 'test_time' 77 } 78 } 79 self.assertDictEqual(update_build_status.get_build_history(['1']), 80 expected_projects) 81 82 def test_get_build_history_missing_log(self, mock_upload_log, 83 mock_cloud_build, mock_google_auth): 84 """Test for missing build log file.""" 85 del mock_cloud_build, mock_google_auth 86 builds = [{'build_id': '1', 'finishTime': 'test_time', 'status': 'SUCCESS'}] 87 mock_get_build = MockGetBuild(builds) 88 update_build_status.get_build = mock_get_build.get_build 89 mock_upload_log.return_value = False 90 self.assertRaises(update_build_status.MissingBuildLogError, 91 update_build_status.get_build_history, ['1']) 92 93 def test_get_build_history_no_last_success(self, mock_upload_log, 94 mock_cloud_build, 95 mock_google_auth): 96 """Test when there is no last successful build.""" 97 del mock_cloud_build, mock_google_auth 98 builds = [{'build_id': '1', 'finishTime': 'test_time', 'status': 'FAILURE'}] 99 mock_get_build = MockGetBuild(builds) 100 update_build_status.get_build = mock_get_build.get_build 101 mock_upload_log.return_value = True 102 103 expected_projects = { 104 'history': [{ 105 'build_id': '1', 106 'finish_time': 'test_time', 107 'success': False 108 }] 109 } 110 self.assertDictEqual(update_build_status.get_build_history(['1']), 111 expected_projects) 112 113 114class TestSortProjects(unittest.TestCase): 115 """Unit tests for testing sorting functionality.""" 116 117 def test_sort_projects(self): 118 """Test sorting functionality.""" 119 projects = [{ 120 'name': '1', 121 'history': [] 122 }, { 123 'name': '2', 124 'history': [{ 125 'success': True 126 }] 127 }, { 128 'name': '3', 129 'history': [{ 130 'success': False 131 }] 132 }] 133 expected_order = ['3', '2', '1'] 134 update_build_status.sort_projects(projects) 135 self.assertEqual(expected_order, [project['name'] for project in projects]) 136 137 138class TestUpdateLastSuccessfulBuild(unittest.TestCase): 139 """Unit tests for updating last successful build.""" 140 141 @classmethod 142 def setUpClass(cls): 143 cls.ds_emulator = test_utils.start_datastore_emulator() 144 test_utils.wait_for_emulator_ready(cls.ds_emulator, 'datastore', 145 test_utils.DATASTORE_READY_INDICATOR) 146 test_utils.set_gcp_environment() 147 148 def setUp(self): 149 test_utils.reset_ds_emulator() 150 151 def test_update_last_successful_build_new(self): 152 """When last successful build isn't available in datastore.""" 153 with ndb.Client().context(): 154 project = { 155 'name': 'test-project', 156 'last_successful_build': { 157 'build_id': '1', 158 'finish_time': 'test_time' 159 } 160 } 161 update_build_status.update_last_successful_build(project, 'fuzzing') 162 expected_build_id = '1' 163 self.assertEqual( 164 expected_build_id, 165 ndb.Key(LastSuccessfulBuild, 'test-project-fuzzing').get().build_id) 166 167 def test_update_last_successful_build_datastore(self): 168 """When last successful build is only available in datastore.""" 169 with ndb.Client().context(): 170 project = {'name': 'test-project'} 171 LastSuccessfulBuild(id='test-project-fuzzing', 172 build_tag='fuzzing', 173 project='test-project', 174 build_id='1', 175 finish_time='test_time').put() 176 177 update_build_status.update_last_successful_build(project, 'fuzzing') 178 expected_project = { 179 'name': 'test-project', 180 'last_successful_build': { 181 'build_id': '1', 182 'finish_time': 'test_time' 183 } 184 } 185 self.assertDictEqual(project, expected_project) 186 187 def test_update_last_successful_build(self): 188 """When last successful build is available at both places.""" 189 with ndb.Client().context(): 190 project = { 191 'name': 'test-project', 192 'last_successful_build': { 193 'build_id': '2', 194 'finish_time': 'test_time' 195 } 196 } 197 LastSuccessfulBuild(id='test-project-fuzzing', 198 build_tag='fuzzing', 199 project='test-project', 200 build_id='1', 201 finish_time='test_time').put() 202 203 update_build_status.update_last_successful_build(project, 'fuzzing') 204 expected_build_id = '2' 205 self.assertEqual( 206 expected_build_id, 207 ndb.Key(LastSuccessfulBuild, 'test-project-fuzzing').get().build_id) 208 209 @classmethod 210 def tearDownClass(cls): 211 test_utils.cleanup_emulator(cls.ds_emulator) 212 213 214class TestUpdateBuildStatus(unittest.TestCase): 215 """Unit test for update build status.""" 216 217 @classmethod 218 def setUpClass(cls): 219 cls.ds_emulator = test_utils.start_datastore_emulator() 220 test_utils.wait_for_emulator_ready(cls.ds_emulator, 'datastore', 221 test_utils.DATASTORE_READY_INDICATOR) 222 test_utils.set_gcp_environment() 223 224 def setUp(self): 225 test_utils.reset_ds_emulator() 226 227 # pylint: disable=no-self-use 228 @mock.patch('google.auth.default', return_value=['temp', 'temp']) 229 @mock.patch('update_build_status.build', return_value='cloudbuild') 230 @mock.patch('update_build_status.upload_log') 231 def test_update_build_status(self, mock_upload_log, mock_cloud_build, 232 mock_google_auth): 233 """Testing update build status as a whole.""" 234 del self, mock_cloud_build, mock_google_auth 235 update_build_status.upload_status = MagicMock() 236 mock_upload_log.return_value = True 237 status_filename = 'status.json' 238 with ndb.Client().context(): 239 BuildsHistory(id='test-project-1-fuzzing', 240 build_tag='fuzzing', 241 project='test-project-1', 242 build_ids=['1']).put() 243 244 BuildsHistory(id='test-project-2-fuzzing', 245 build_tag='fuzzing', 246 project='test-project-2', 247 build_ids=['2']).put() 248 249 BuildsHistory(id='test-project-3-fuzzing', 250 build_tag='fuzzing', 251 project='test-project-3', 252 build_ids=['3']).put() 253 254 builds = [{ 255 'build_id': '1', 256 'finishTime': 'test_time', 257 'status': 'SUCCESS' 258 }, { 259 'build_id': '2', 260 'finishTime': 'test_time', 261 'status': 'FAILURE' 262 }, { 263 'build_id': '3', 264 'status': 'WORKING' 265 }] 266 mock_get_build = MockGetBuild(builds) 267 update_build_status.get_build = mock_get_build.get_build 268 269 expected_data = { 270 'projects': [{ 271 'history': [{ 272 'build_id': '2', 273 'finish_time': 'test_time', 274 'success': False 275 }], 276 'name': 'test-project-2' 277 }, { 278 'history': [{ 279 'build_id': '1', 280 'finish_time': 'test_time', 281 'success': True 282 }], 283 'last_successful_build': { 284 'build_id': '1', 285 'finish_time': 'test_time' 286 }, 287 'name': 'test-project-1' 288 }, { 289 'history': [], 290 'name': 'test-project-3' 291 }] 292 } 293 294 update_build_status.update_build_status('fuzzing', 'status.json') 295 update_build_status.upload_status.assert_called_with( 296 expected_data, status_filename) 297 298 @classmethod 299 def tearDownClass(cls): 300 test_utils.cleanup_emulator(cls.ds_emulator) 301 302 303if __name__ == '__main__': 304 unittest.main(exit=False) 305