1*14675a02SAndroid Build Coastguard Worker# Copyright 2022 Google LLC 2*14675a02SAndroid Build Coastguard Worker# 3*14675a02SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 4*14675a02SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 5*14675a02SAndroid Build Coastguard Worker# You may obtain a copy of the License at 6*14675a02SAndroid Build Coastguard Worker# 7*14675a02SAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 8*14675a02SAndroid Build Coastguard Worker# 9*14675a02SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 10*14675a02SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 11*14675a02SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*14675a02SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 13*14675a02SAndroid Build Coastguard Worker# limitations under the License. 14*14675a02SAndroid Build Coastguard Worker"""Tests for http_actions.""" 15*14675a02SAndroid Build Coastguard Worker 16*14675a02SAndroid Build Coastguard Workerimport gzip 17*14675a02SAndroid Build Coastguard Workerimport http 18*14675a02SAndroid Build Coastguard Workerimport http.client 19*14675a02SAndroid Build Coastguard Workerimport http.server 20*14675a02SAndroid Build Coastguard Workerimport socket 21*14675a02SAndroid Build Coastguard Workerimport threading 22*14675a02SAndroid Build Coastguard Workerfrom unittest import mock 23*14675a02SAndroid Build Coastguard Worker 24*14675a02SAndroid Build Coastguard Workerfrom absl.testing import absltest 25*14675a02SAndroid Build Coastguard Worker 26*14675a02SAndroid Build Coastguard Workerfrom fcp.demo import http_actions 27*14675a02SAndroid Build Coastguard Workerfrom fcp.protos.federatedcompute import common_pb2 28*14675a02SAndroid Build Coastguard Workerfrom fcp.protos.federatedcompute import eligibility_eval_tasks_pb2 29*14675a02SAndroid Build Coastguard Worker 30*14675a02SAndroid Build Coastguard Worker 31*14675a02SAndroid Build Coastguard Workerclass TestService: 32*14675a02SAndroid Build Coastguard Worker 33*14675a02SAndroid Build Coastguard Worker def __init__(self): 34*14675a02SAndroid Build Coastguard Worker self.proto_action = mock.Mock() 35*14675a02SAndroid Build Coastguard Worker self.get_action = mock.Mock() 36*14675a02SAndroid Build Coastguard Worker self.post_action = mock.Mock() 37*14675a02SAndroid Build Coastguard Worker 38*14675a02SAndroid Build Coastguard Worker @http_actions.proto_action( 39*14675a02SAndroid Build Coastguard Worker service='google.internal.federatedcompute.v1.EligibilityEvalTasks', 40*14675a02SAndroid Build Coastguard Worker method='RequestEligibilityEvalTask') 41*14675a02SAndroid Build Coastguard Worker def handle_proto_action(self, *args, **kwargs): 42*14675a02SAndroid Build Coastguard Worker return self.proto_action(*args, **kwargs) 43*14675a02SAndroid Build Coastguard Worker 44*14675a02SAndroid Build Coastguard Worker @http_actions.http_action(method='get', pattern='/get/{arg1}/{arg2}') 45*14675a02SAndroid Build Coastguard Worker def handle_get_action(self, *args, **kwargs): 46*14675a02SAndroid Build Coastguard Worker return self.get_action(*args, **kwargs) 47*14675a02SAndroid Build Coastguard Worker 48*14675a02SAndroid Build Coastguard Worker @http_actions.http_action(method='post', pattern='/post/{arg1}/{arg2}') 49*14675a02SAndroid Build Coastguard Worker def handle_post_action(self, *args, **kwargs): 50*14675a02SAndroid Build Coastguard Worker return self.post_action(*args, **kwargs) 51*14675a02SAndroid Build Coastguard Worker 52*14675a02SAndroid Build Coastguard Worker 53*14675a02SAndroid Build Coastguard Workerclass TestHttpServer(http.server.HTTPServer): 54*14675a02SAndroid Build Coastguard Worker pass 55*14675a02SAndroid Build Coastguard Worker 56*14675a02SAndroid Build Coastguard Worker 57*14675a02SAndroid Build Coastguard Workerclass HttpActionsTest(absltest.TestCase): 58*14675a02SAndroid Build Coastguard Worker 59*14675a02SAndroid Build Coastguard Worker def setUp(self): 60*14675a02SAndroid Build Coastguard Worker super().setUp() 61*14675a02SAndroid Build Coastguard Worker self.service = TestService() 62*14675a02SAndroid Build Coastguard Worker handler = http_actions.create_handler(self.service) 63*14675a02SAndroid Build Coastguard Worker self._httpd = TestHttpServer(('localhost', 0), handler) 64*14675a02SAndroid Build Coastguard Worker self._server_thread = threading.Thread( 65*14675a02SAndroid Build Coastguard Worker target=self._httpd.serve_forever, daemon=True) 66*14675a02SAndroid Build Coastguard Worker self._server_thread.start() 67*14675a02SAndroid Build Coastguard Worker self.conn = http.client.HTTPConnection( 68*14675a02SAndroid Build Coastguard Worker self._httpd.server_name, port=self._httpd.server_port) 69*14675a02SAndroid Build Coastguard Worker 70*14675a02SAndroid Build Coastguard Worker def tearDown(self): 71*14675a02SAndroid Build Coastguard Worker self._httpd.shutdown() 72*14675a02SAndroid Build Coastguard Worker self._server_thread.join() 73*14675a02SAndroid Build Coastguard Worker self._httpd.server_close() 74*14675a02SAndroid Build Coastguard Worker super().tearDown() 75*14675a02SAndroid Build Coastguard Worker 76*14675a02SAndroid Build Coastguard Worker def test_not_found(self): 77*14675a02SAndroid Build Coastguard Worker self.conn.request('GET', '/no-match') 78*14675a02SAndroid Build Coastguard Worker self.assertEqual(self.conn.getresponse().status, http.HTTPStatus.NOT_FOUND) 79*14675a02SAndroid Build Coastguard Worker 80*14675a02SAndroid Build Coastguard Worker def test_proto_success(self): 81*14675a02SAndroid Build Coastguard Worker expected_response = ( 82*14675a02SAndroid Build Coastguard Worker eligibility_eval_tasks_pb2.EligibilityEvalTaskResponse( 83*14675a02SAndroid Build Coastguard Worker session_id='test')) 84*14675a02SAndroid Build Coastguard Worker self.service.proto_action.return_value = expected_response 85*14675a02SAndroid Build Coastguard Worker 86*14675a02SAndroid Build Coastguard Worker request = eligibility_eval_tasks_pb2.EligibilityEvalTaskRequest( 87*14675a02SAndroid Build Coastguard Worker client_version=common_pb2.ClientVersion(version_code='test123')) 88*14675a02SAndroid Build Coastguard Worker self.conn.request( 89*14675a02SAndroid Build Coastguard Worker 'POST', 90*14675a02SAndroid Build Coastguard Worker '/v1/eligibilityevaltasks/test%2Fpopulation:request?%24alt=proto', 91*14675a02SAndroid Build Coastguard Worker request.SerializeToString()) 92*14675a02SAndroid Build Coastguard Worker response = self.conn.getresponse() 93*14675a02SAndroid Build Coastguard Worker self.assertEqual(response.status, http.HTTPStatus.OK) 94*14675a02SAndroid Build Coastguard Worker response_proto = ( 95*14675a02SAndroid Build Coastguard Worker eligibility_eval_tasks_pb2.EligibilityEvalTaskResponse.FromString( 96*14675a02SAndroid Build Coastguard Worker response.read())) 97*14675a02SAndroid Build Coastguard Worker self.assertEqual(response_proto, expected_response) 98*14675a02SAndroid Build Coastguard Worker # `population_name` should be set from the URL. 99*14675a02SAndroid Build Coastguard Worker request.population_name = 'test/population' 100*14675a02SAndroid Build Coastguard Worker self.service.proto_action.assert_called_once_with(request) 101*14675a02SAndroid Build Coastguard Worker 102*14675a02SAndroid Build Coastguard Worker def test_proto_error(self): 103*14675a02SAndroid Build Coastguard Worker self.service.proto_action.side_effect = http_actions.HttpError( 104*14675a02SAndroid Build Coastguard Worker code=http.HTTPStatus.UNAUTHORIZED) 105*14675a02SAndroid Build Coastguard Worker 106*14675a02SAndroid Build Coastguard Worker self.conn.request( 107*14675a02SAndroid Build Coastguard Worker 'POST', 108*14675a02SAndroid Build Coastguard Worker '/v1/eligibilityevaltasks/test%2Fpopulation:request?%24alt=proto', b'') 109*14675a02SAndroid Build Coastguard Worker response = self.conn.getresponse() 110*14675a02SAndroid Build Coastguard Worker self.assertEqual(response.status, http.HTTPStatus.UNAUTHORIZED) 111*14675a02SAndroid Build Coastguard Worker 112*14675a02SAndroid Build Coastguard Worker def test_proto_with_invalid_payload(self): 113*14675a02SAndroid Build Coastguard Worker self.conn.request( 114*14675a02SAndroid Build Coastguard Worker 'POST', 115*14675a02SAndroid Build Coastguard Worker '/v1/eligibilityevaltasks/test%2Fpopulation:request?%24alt=proto', 116*14675a02SAndroid Build Coastguard Worker b'invalid') 117*14675a02SAndroid Build Coastguard Worker response = self.conn.getresponse() 118*14675a02SAndroid Build Coastguard Worker self.assertEqual(response.status, http.HTTPStatus.BAD_REQUEST) 119*14675a02SAndroid Build Coastguard Worker 120*14675a02SAndroid Build Coastguard Worker def test_proto_with_gzip_encoding(self): 121*14675a02SAndroid Build Coastguard Worker self.service.proto_action.return_value = ( 122*14675a02SAndroid Build Coastguard Worker eligibility_eval_tasks_pb2.EligibilityEvalTaskResponse()) 123*14675a02SAndroid Build Coastguard Worker 124*14675a02SAndroid Build Coastguard Worker request = eligibility_eval_tasks_pb2.EligibilityEvalTaskRequest( 125*14675a02SAndroid Build Coastguard Worker client_version=common_pb2.ClientVersion(version_code='test123')) 126*14675a02SAndroid Build Coastguard Worker self.conn.request('POST', 127*14675a02SAndroid Build Coastguard Worker '/v1/eligibilityevaltasks/test:request?%24alt=proto', 128*14675a02SAndroid Build Coastguard Worker gzip.compress(request.SerializeToString()), 129*14675a02SAndroid Build Coastguard Worker {'Content-Encoding': 'gzip'}) 130*14675a02SAndroid Build Coastguard Worker self.assertEqual(self.conn.getresponse().status, http.HTTPStatus.OK) 131*14675a02SAndroid Build Coastguard Worker request.population_name = 'test' 132*14675a02SAndroid Build Coastguard Worker self.service.proto_action.assert_called_once_with(request) 133*14675a02SAndroid Build Coastguard Worker 134*14675a02SAndroid Build Coastguard Worker def test_proto_with_invalid_gzip_encoding(self): 135*14675a02SAndroid Build Coastguard Worker self.conn.request('POST', 136*14675a02SAndroid Build Coastguard Worker '/v1/eligibilityevaltasks/test:request?%24alt=proto', 137*14675a02SAndroid Build Coastguard Worker b'invalid', {'Content-Encoding': 'gzip'}) 138*14675a02SAndroid Build Coastguard Worker response = self.conn.getresponse() 139*14675a02SAndroid Build Coastguard Worker self.assertEqual(response.status, http.HTTPStatus.BAD_REQUEST) 140*14675a02SAndroid Build Coastguard Worker 141*14675a02SAndroid Build Coastguard Worker def test_proto_with_unsupport_encoding(self): 142*14675a02SAndroid Build Coastguard Worker self.conn.request('POST', 143*14675a02SAndroid Build Coastguard Worker '/v1/eligibilityevaltasks/test:request?%24alt=proto', b'', 144*14675a02SAndroid Build Coastguard Worker {'Content-Encoding': 'compress'}) 145*14675a02SAndroid Build Coastguard Worker self.assertEqual(self.conn.getresponse().status, 146*14675a02SAndroid Build Coastguard Worker http.HTTPStatus.BAD_REQUEST) 147*14675a02SAndroid Build Coastguard Worker self.service.proto_action.assert_not_called() 148*14675a02SAndroid Build Coastguard Worker 149*14675a02SAndroid Build Coastguard Worker def test_get_success(self): 150*14675a02SAndroid Build Coastguard Worker self.service.get_action.return_value = http_actions.HttpResponse( 151*14675a02SAndroid Build Coastguard Worker body=b'body', 152*14675a02SAndroid Build Coastguard Worker headers={ 153*14675a02SAndroid Build Coastguard Worker 'Content-Length': 4, 154*14675a02SAndroid Build Coastguard Worker 'Content-Type': 'application/x-test', 155*14675a02SAndroid Build Coastguard Worker }) 156*14675a02SAndroid Build Coastguard Worker 157*14675a02SAndroid Build Coastguard Worker self.conn.request('GET', '/get/foo/bar') 158*14675a02SAndroid Build Coastguard Worker response = self.conn.getresponse() 159*14675a02SAndroid Build Coastguard Worker self.assertEqual(response.status, http.HTTPStatus.OK) 160*14675a02SAndroid Build Coastguard Worker self.assertEqual(response.headers['Content-Length'], '4') 161*14675a02SAndroid Build Coastguard Worker self.assertEqual(response.headers['Content-Type'], 'application/x-test') 162*14675a02SAndroid Build Coastguard Worker self.assertEqual(response.read(), b'body') 163*14675a02SAndroid Build Coastguard Worker self.service.get_action.assert_called_once_with(b'', arg1='foo', arg2='bar') 164*14675a02SAndroid Build Coastguard Worker 165*14675a02SAndroid Build Coastguard Worker def test_get_error(self): 166*14675a02SAndroid Build Coastguard Worker self.service.get_action.side_effect = http_actions.HttpError( 167*14675a02SAndroid Build Coastguard Worker code=http.HTTPStatus.UNAUTHORIZED) 168*14675a02SAndroid Build Coastguard Worker 169*14675a02SAndroid Build Coastguard Worker self.conn.request('GET', '/get/foo/bar') 170*14675a02SAndroid Build Coastguard Worker self.assertEqual(self.conn.getresponse().status, 171*14675a02SAndroid Build Coastguard Worker http.HTTPStatus.UNAUTHORIZED) 172*14675a02SAndroid Build Coastguard Worker 173*14675a02SAndroid Build Coastguard Worker def test_post_success(self): 174*14675a02SAndroid Build Coastguard Worker self.service.post_action.return_value = http_actions.HttpResponse( 175*14675a02SAndroid Build Coastguard Worker body=b'body', 176*14675a02SAndroid Build Coastguard Worker headers={ 177*14675a02SAndroid Build Coastguard Worker 'Content-Length': 4, 178*14675a02SAndroid Build Coastguard Worker 'Content-Type': 'application/x-test', 179*14675a02SAndroid Build Coastguard Worker }) 180*14675a02SAndroid Build Coastguard Worker 181*14675a02SAndroid Build Coastguard Worker self.conn.request('POST', '/post/foo/bar', b'request-body') 182*14675a02SAndroid Build Coastguard Worker response = self.conn.getresponse() 183*14675a02SAndroid Build Coastguard Worker self.assertEqual(response.status, http.HTTPStatus.OK) 184*14675a02SAndroid Build Coastguard Worker self.assertEqual(response.headers['Content-Length'], '4') 185*14675a02SAndroid Build Coastguard Worker self.assertEqual(response.headers['Content-Type'], 'application/x-test') 186*14675a02SAndroid Build Coastguard Worker self.assertEqual(response.read(), b'body') 187*14675a02SAndroid Build Coastguard Worker self.service.post_action.assert_called_once_with( 188*14675a02SAndroid Build Coastguard Worker b'request-body', arg1='foo', arg2='bar') 189*14675a02SAndroid Build Coastguard Worker 190*14675a02SAndroid Build Coastguard Worker def test_post_error(self): 191*14675a02SAndroid Build Coastguard Worker self.service.post_action.side_effect = http_actions.HttpError( 192*14675a02SAndroid Build Coastguard Worker code=http.HTTPStatus.UNAUTHORIZED) 193*14675a02SAndroid Build Coastguard Worker 194*14675a02SAndroid Build Coastguard Worker self.conn.request('POST', '/post/foo/bar', b'request-body') 195*14675a02SAndroid Build Coastguard Worker self.assertEqual(self.conn.getresponse().status, 196*14675a02SAndroid Build Coastguard Worker http.HTTPStatus.UNAUTHORIZED) 197*14675a02SAndroid Build Coastguard Worker 198*14675a02SAndroid Build Coastguard Worker def test_post_with_gzip_encoding(self): 199*14675a02SAndroid Build Coastguard Worker self.service.post_action.return_value = http_actions.HttpResponse(body=b'') 200*14675a02SAndroid Build Coastguard Worker 201*14675a02SAndroid Build Coastguard Worker self.conn.request('POST', '/post/foo/bar', gzip.compress(b'request-body'), 202*14675a02SAndroid Build Coastguard Worker {'Content-Encoding': 'gzip'}) 203*14675a02SAndroid Build Coastguard Worker self.assertEqual(self.conn.getresponse().status, http.HTTPStatus.OK) 204*14675a02SAndroid Build Coastguard Worker self.service.post_action.assert_called_once_with( 205*14675a02SAndroid Build Coastguard Worker b'request-body', arg1='foo', arg2='bar') 206*14675a02SAndroid Build Coastguard Worker 207*14675a02SAndroid Build Coastguard Worker def test_post_with_unsupport_encoding(self): 208*14675a02SAndroid Build Coastguard Worker self.conn.request('POST', '/post/foo/bar', b'', 209*14675a02SAndroid Build Coastguard Worker {'Content-Encoding': 'compress'}) 210*14675a02SAndroid Build Coastguard Worker self.assertEqual(self.conn.getresponse().status, 211*14675a02SAndroid Build Coastguard Worker http.HTTPStatus.BAD_REQUEST) 212*14675a02SAndroid Build Coastguard Worker self.service.post_action.assert_not_called() 213*14675a02SAndroid Build Coastguard Worker 214*14675a02SAndroid Build Coastguard Worker 215*14675a02SAndroid Build Coastguard Workerif __name__ == '__main__': 216*14675a02SAndroid Build Coastguard Worker absltest.main() 217