Project import generated by Copybara.

GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/api/test_call b/api/test_call
new file mode 100755
index 0000000..5455241
--- /dev/null
+++ b/api/test_call
@@ -0,0 +1,157 @@
+#!/usr/bin/env python
+"""
+This is a helper script for making pRPC API calls during local development.
+
+Usage examples:
+
+To test an anonymous request to your own local monorail server:
+1. Run 'make serve' in another shell
+2. `./api/test_call monorail.Projects ListComponents
+     '{"project_name": "monorail", "include_admin_info": true}'`
+
+To test a signed in request to your own local monorail server:
+1. Run 'make serve' in another shell
+2. `./api/test_call monorail.Projects ListComponents
+     '{"project_name": "monorail", "include_admin_info": true}'
+     --test_account=test@example.com`
+Note that test account email address must always end in @example.com.
+
+To test an anonymous request to your monorail staging server:
+1. Deploy your staging server version, e.g., 12345-76697e9-tainted-jrobbins.
+2. Visit your staging server in a new incognito window and view source
+   to find the XSRF token for the anonymous user in JS var CS_env['token'].
+3. `./api/test_call monorail.Projects ListComponents
+    '{"project_name": "monorail", "include_admin_info": true}'
+    --host=12345-76697e9-tainted-jrobbins-dot-monorail-staging.appspot.com
+    --xsrf-token='THE_ANON_TOKEN'`
+
+To test a signed-in request to your monorail staging server using
+the client_id for monorail-staging and your own account:
+1. Make sure that you have a role in the monorail-staging project.
+2. Have your account allowlisted by email address.
+3. Download the monorail-staging app credientials via
+   `gcloud --project=monorail-staging auth login`.
+4. `./api/test_call monorail.Projects ListComponents
+    '{"project_name": "monorail", "include_admin_info": true}'
+    --host=12345-76697e9-tainted-jrobbins-dot-monorail-staging.appspot.com
+    --use-app-credentials`
+
+To test a signed-in request to your monorail staging server using
+a service account client secrets file that you download:
+(Note: This is not recommended for prod because downloading secrets
+is a bad practice.)
+1. Create a service account via the Cloud Console for any project.
+   Choose "IAM & Admin" > "Service accounts".
+   Press "+ Create Service Account".
+   Fill in the form and submit it to save a service account .json file
+   to your local disk.  Keep this file private.
+2. File an issue on /p/monorail to allowlist your client_id and/or
+   client_email.  Or, author a CL yourself to add it to the allowlist.
+3. `./api/test_call monorail.Projects ListComponents
+    '{"project_name": "monorail", "include_admin_info": true}'
+    --host=12345-76697e9-tainted-jrobbins-dot-monorail-staging.appspot.com
+    --service-account=FILENAME_OF_SERVICE_ACCOUNT_JSON_FILE`
+"""
+
+import argparse
+import errno
+import json
+import logging
+import os
+import sys
+
+
+monorail_dir = os.path.dirname(os.path.abspath(__file__ + '/..'))
+third_party_path = os.path.join(monorail_dir, 'third_party')
+if third_party_path not in sys.path:
+  sys.path.insert(0, third_party_path)
+
+import httplib2
+from oauth2client.client import GoogleCredentials
+
+
+URL_BASE = 'http://localhost:8080/prpc/'
+OAUTH_SCOPE = 'https://www.googleapis.com/auth/userinfo.email'
+
+def make_http(args):
+  """Return an httplib2.Http object, with or without oauth."""
+  http = httplib2.Http()
+  credentials = None
+  if args.use_app_credentials:
+    credentials = GoogleCredentials.get_application_default()
+  if args.service_account:
+    credentials = GoogleCredentials.from_stream(args.service_account)
+    logging.debug('Will request as user %r', credentials.service_account_email)
+
+  if credentials:
+    credentials = credentials.create_scoped([OAUTH_SCOPE])
+    logging.debug('Will request as client %r', credentials.client_id)
+    if not args.host:
+      print(('[ERROR] OAuth on localhost will always see user '
+             'example@example.com, so we do not support that.\n'
+             'Instead, add --server=YOUR_STAGING_SERVER, '
+             'or use --test_account=USER@example.com.'))
+      sys.exit(1)
+
+    http = credentials.authorize(http)
+
+  return http
+
+def make_call(service, method, json_body, args):
+  """Call the server and print the response contents."""
+  body = json.loads(json_body)
+
+  url_base = URL_BASE
+  if args.host:
+    url_base = 'https://%s/prpc/' % args.host
+  url = '%s%s/%s' % (url_base, service, method)
+  logging.debug('Request URL: %s', url)
+
+  http = make_http(args)
+  headers = {
+      'Content-Type': 'application/json',
+      'Accept': 'application/json',
+      }
+  if args.test_account:
+    headers['x-test-account'] = args.test_account
+  if args.xsrf_token:
+    headers['x-xsrf-token'] = args.xsrf_token
+  body = json.dumps(body)
+
+  logging.debug('Body: %r' % body)
+  try:
+    response, contents = http.request(
+        url, method='POST', body=body, headers=headers)
+    logging.info('Received response: %s', contents)
+  except httplib2.HttpLib2Error as e:
+    if hasattr(e.reason, 'errno') and e.reason.errno == errno.ECONNREFUSED:
+      print('[Error] Could not reach server. Is it running?')
+    else:
+      raise e
+
+
+if __name__ == '__main__':
+  parser = argparse.ArgumentParser(description='Process some integers.')
+  parser.add_argument('service', help='pRPC service name.')
+  parser.add_argument('method', help='pRPC method name.')
+  parser.add_argument('json_body', help='pRPC HTTP body in valid JSON.')
+  parser.add_argument('--test-account',
+      help='Test account to use, in the form of an email.')
+  parser.add_argument('--xsrf-token', help='Custom XSRF token.')
+  parser.add_argument('--host', help='remote server FQDN.')
+  parser.add_argument(
+      '--use-app-credentials',
+      help='Use credentials of a GAE app that you are signed into via gcloud.',
+      action='store_true')
+  parser.add_argument(
+      '--service-account', help='Service account credentials JSON file name.')
+  parser.add_argument('-v', '--verbose', action='store_true')
+  args = parser.parse_args()
+
+  if args.verbose:
+    log_level = logging.DEBUG
+  else:
+    log_level = logging.INFO
+  logging.basicConfig(format='%(levelname)s: %(message)s', level=log_level)
+
+  make_call(args.service, args.method, args.json_body, args)