1#!/usr/bin/env python
2#
3# Copyright (C) 2017 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18import unittest
19from host_controller.build import build_provider_pab
20
21try:
22    from unittest import mock
23except ImportError:
24    import mock
25
26from requests.models import Response
27
28
29class BuildProviderPABTest(unittest.TestCase):
30    """Tests for Partner Android Build client."""
31
32    def setUp(self):
33        self.client = build_provider_pab.BuildProviderPAB()
34        self.client.XSRF_STORE = None
35
36    def tearDown(self):
37        del self.client
38
39    @mock.patch("build_provider_pab.flow_from_clientsecrets")
40    @mock.patch("build_provider_pab.run_flow")
41    @mock.patch("build_provider_pab.Storage.get")
42    @mock.patch('build_provider_pab.BuildProviderPAB._credentials')
43    def testAuthenticationNew(self, mock_creds, mock_storage_get, mock_rf,
44                              mock_ffc):
45        mock_creds.invalid = True
46        build_provider_pab.flow_from_clientsecrets = mock_ffc
47        build_provider_pab.run_flow = mock_rf
48        self.client.Authenticate()
49        mock_ffc.assert_called_once()
50        mock_storage_get.assert_called_once()
51        mock_rf.assert_called_once()
52
53    @mock.patch("build_provider_pab.flow_from_clientsecrets")
54    @mock.patch("build_provider_pab.run_flow")
55    @mock.patch("build_provider_pab.Storage.get")
56    @mock.patch('build_provider_pab.BuildProviderPAB._credentials')
57    def testAuthenticationStale(self, mock_creds, mock_storage_get, mock_rf,
58                                mock_ffc):
59        mock_creds.invalid = False
60        mock_creds.access_token_expired = True
61        build_provider_pab.flow_from_clientsecrets = mock_ffc
62        build_provider_pab.run_flow = mock_rf
63        mock_storage_get.return_value = mock_creds
64        self.client.Authenticate()
65        mock_ffc.assert_called_once()
66        mock_storage_get.assert_called_once()
67        mock_rf.assert_not_called()
68        mock_creds.refresh.assert_called_once()
69
70    @mock.patch("build_provider_pab.flow_from_clientsecrets")
71    @mock.patch("build_provider_pab.run_flow")
72    @mock.patch("build_provider_pab.Storage.get")
73    @mock.patch('build_provider_pab.BuildProviderPAB._credentials')
74    def testAuthenticationFresh(self, mock_creds, mock_storage_get, mock_rf,
75                                mock_ffc):
76        mock_creds.invalid = False
77        mock_creds.access_token_expired = False
78        build_provider_pab.flow_from_clientsecrets = mock_ffc
79        build_provider_pab.run_flow = mock_rf
80        mock_storage_get.return_value = mock_creds
81        self.client.Authenticate()
82        mock_ffc.assert_called_once()
83        mock_storage_get.assert_called_once()
84        mock_rf.assert_not_called()
85        mock_creds.refresh.assert_not_called()
86
87    @mock.patch('build_provider_pab.BuildProviderPAB._credentials')
88    @mock.patch('requests.get')
89    @mock.patch('__builtin__.open')
90    def testDownloadArtifact(self, mock_open, mock_get, mock_creds):
91        self.client._credentials = mock_creds
92        artifact_url = (
93            "https://partnerdash.google.com/build/gmsdownload/"
94            "f_companion/label/clockwork.companion_20170906_211311_RC00/"
95            "ClockworkCompanionGoogleWithGmsRelease_signed.apk?a=100621237")
96        self.client.DownloadArtifact(
97            artifact_url, 'ClockworkCompanionGoogleWithGmsRelease_signed.apk')
98        self.client._credentials.apply.assert_called_with({})
99        mock_get.assert_called_with(
100            artifact_url, headers={}, stream=True)
101        mock_open.assert_called_with(
102            'ClockworkCompanionGoogleWithGmsRelease_signed.apk', 'wb')
103
104    @mock.patch('build_provider_pab.BuildProviderPAB._credentials')
105    @mock.patch('requests.post')
106    def testGetArtifactURL(self, mock_post, mock_creds):
107        self.client._xsrf = 'disable'
108        response = Response()
109        response.status_code = 200
110        response._content = b'{ "result" : {"1": "this_url"}}'
111        mock_post.return_value = response
112        self.client._credentials = mock_creds
113        url = self.client.GetArtifactURL(
114            100621237,
115            "4331445",
116            "darwin_mac",
117            "android-ndk-43345-darwin-x86_64.tar.bz2",
118            "aosp-master-ndk",
119            0,
120            method='POST')
121        mock_post.assert_called_with(
122            'https://partner.android.com/build/u/0/_gwt/_rpc/buildsvc',
123            data=mock.ANY,
124            headers={
125                'Content-Type': 'application/json',
126                'x-alkali-account': 100621237,
127            })
128        self.assertEqual(url, "this_url")
129
130    @mock.patch('build_provider_pab.BuildProviderPAB._credentials')
131    @mock.patch('requests.post')
132    def testGetArtifactURLBackendError(self, mock_post, mock_creds):
133        self.client._xsrf = 'disable'
134        response = Response()
135        response.status_code = 200
136        response._content = b'not JSON'
137        mock_post.return_value = response
138        self.client._credentials = mock_creds
139        with self.assertRaises(ValueError) as cm:
140            self.client.GetArtifactURL(
141                100621237,
142                "4331445",
143                "darwin_mac",
144                "android-ndk-43345-darwin-x86_64.tar.bz2",
145                "aosp-master-ndk",
146                0,
147                method='POST')
148        expected = "Backend error -- check your account ID"
149        self.assertEqual(str(cm.exception), expected)
150
151    @mock.patch('build_provider_pab.BuildProviderPAB._credentials')
152    @mock.patch('requests.post')
153    def testGetArtifactURLMissingResultError(self, mock_post, mock_creds):
154        self.client._xsrf = 'disable'
155        response = Response()
156        response.status_code = 200
157        response._content = b'{"result": {}}'
158        mock_post.return_value = response
159        self.client._credentials = mock_creds
160        with self.assertRaises(ValueError) as cm:
161            self.client.GetArtifactURL(
162                100621237,
163                "4331445",
164                "darwin_mac",
165                "android-ndk-43345-darwin-x86_64.tar.bz2",
166                "aosp-master-ndk",
167                0,
168                method='POST')
169        expected = "Resource not found"
170        self.assertIn(expected, str(cm.exception))
171
172    @mock.patch('build_provider_pab.BuildProviderPAB._credentials')
173    @mock.patch('requests.post')
174    def testGetArtifactURLInvalidXSRFError(self, mock_post, mock_creds):
175        self.client._xsrf = 'disable'
176        response = Response()
177        response.status_code = 200
178        response._content = b'{"error": {"code": -32000, "message":"Invalid"}}'
179        mock_post.return_value = response
180        self.client._credentials = mock_creds
181        with self.assertRaises(ValueError) as cm:
182            self.client.GetArtifactURL(
183                100621237,
184                "4331445",
185                "darwin_mac",
186                "android-ndk-43345-darwin-x86_64.tar.bz2",
187                "aosp-master-ndk",
188                0,
189                method='POST')
190        self.assertIn('Bad XSRF token', str(cm.exception))
191
192    @mock.patch('build_provider_pab.BuildProviderPAB._credentials')
193    @mock.patch('requests.post')
194    def testGetArtifactURLExpiredXSRFError(self, mock_post, mock_creds):
195        self.client._xsrf = 'disable'
196        response = Response()
197        response.status_code = 200
198        response._content = b'{"error": {"code": -32001, "message":"Expired"}}'
199        mock_post.return_value = response
200        self.client._credentials = mock_creds
201        with self.assertRaises(ValueError) as cm:
202            self.client.GetArtifactURL(
203                100621237,
204                "4331445",
205                "darwin_mac",
206                "android-ndk-43345-darwin-x86_64.tar.bz2",
207                "aosp-master-ndk",
208                0,
209                method='POST')
210        self.assertIn('Expired XSRF token', str(cm.exception))
211
212    @mock.patch('build_provider_pab.BuildProviderPAB._credentials')
213    @mock.patch('requests.post')
214    def testGetArtifactURLUnknownError(self, mock_post, mock_creds):
215        self.client._xsrf = 'disable'
216        response = Response()
217        response.status_code = 200
218        response._content = b'{"some_other_json": "foo"}'
219        mock_post.return_value = response
220        self.client._credentials = mock_creds
221        with self.assertRaises(ValueError) as cm:
222            self.client.GetArtifactURL(
223                100621237,
224                "4331445",
225                "darwin_mac",
226                "android-ndk-43345-darwin-x86_64.tar.bz2",
227                "aosp-master-ndk",
228                0,
229                method='POST')
230        self.assertIn('Unknown response from server', str(cm.exception))
231
232    @mock.patch('build_provider_pab.BuildProviderPAB._credentials')
233    @mock.patch('requests.post')
234    def testGetBuildListSuccess(self, mock_post, mock_creds):
235        self.client._xsrf = 'disable'
236        response = Response()
237        response.status_code = 200
238        response._content = b'{"result": {"1": "foo"}}'
239        mock_post.return_value = response
240        self.client._credentials = mock_creds
241        result = self.client.GetBuildList(
242            100621237,
243            "git_oc-treble-dev",
244            "aosp_arm64_ab-userdebug",
245            method='POST')
246        self.assertEqual(result, "foo")
247        mock_post.assert_called_with(
248            'https://partner.android.com/build/u/0/_gwt/_rpc/buildsvc',
249            data=mock.ANY,
250            headers={
251                'Content-Type': 'application/json',
252                'x-alkali-account': 100621237,
253            })
254
255    @mock.patch('build_provider_pab.BuildProviderPAB._credentials')
256    @mock.patch('requests.post')
257    def testGetBuildListError(self, mock_post, mock_creds):
258        self.client._xsrf = 'disable'
259        response = Response()
260        response.status_code = 200
261        response._content = b'{"result": {"3": "foo"}}'
262        mock_post.return_value = response
263        self.client._credentials = mock_creds
264        with self.assertRaises(ValueError) as cm:
265            self.client.GetBuildList(
266                100621237,
267                "git_oc-treble-dev",
268                "aosp_arm64_ab-userdebug",
269                method='POST')
270        self.assertIn('Build list not found', str(cm.exception))
271
272    @mock.patch('build_provider_pab.BuildProviderPAB._credentials')
273    @mock.patch('build_provider_pab.BuildProviderPAB.GetBuildList')
274    def testGetLatestBuildIdSuccess(self, mock_gbl, mock_creds):
275        self.client._xsrf = 'disable'
276        mock_gbl.return_value = [{'7': 5, '1': 'bad'}, {'7': 7, '1': 'good'}]
277        self.client.GetBuildList = mock_gbl
278        result = self.client.GetLatestBuildId(
279            100621237,
280            "git_oc-treble-dev",
281            "aosp_arm64_ab-userdebug",
282            method='POST')
283        self.assertEqual(result, 'good')
284
285    @mock.patch('build_provider_pab.BuildProviderPAB._credentials')
286    @mock.patch('build_provider_pab.BuildProviderPAB.GetBuildList')
287    def testGetLatestBuildIdEmpty(self, mock_gbl, mock_creds):
288        self.client._xsrf = 'disable'
289        mock_gbl.return_value = []
290        self.client.GetBuildList = mock_gbl
291        with self.assertRaises(ValueError) as cm:
292            result = self.client.GetLatestBuildId(
293                100621237,
294                "git_oc-treble-dev",
295                "aosp_arm64_ab-userdebug",
296                method='POST')
297        self.assertIn("No builds found for", str(cm.exception))
298
299    @mock.patch('build_provider_pab.BuildProviderPAB._credentials')
300    @mock.patch('build_provider_pab.BuildProviderPAB.GetBuildList')
301    def testGetLatestBuildIdAllBad(self, mock_gbl, mock_creds):
302        self.client._xsrf = 'disable'
303        mock_gbl.return_value = [{'7': 0}, {'7': 0}]
304        self.client.GetBuildList = mock_gbl
305        with self.assertRaises(ValueError) as cm:
306            result = self.client.GetLatestBuildId(
307                100621237,
308                "git_oc-treble-dev",
309                "aosp_arm64_ab-userdebug",
310                method='POST')
311        self.assertEqual(
312            "No complete builds found: 2 failed or incomplete builds found",
313            str(cm.exception))
314
315
316if __name__ == "__main__":
317    unittest.main()
318