Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nox system test refactor #60

Merged
merged 12 commits into from
Oct 28, 2016
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ dist/
docs/_build

# Test files
.nox/
.tox/
.cache/

Expand Down
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ matrix:
- python: 3.5
env: TOXENV=cover
- python: 3.5
env: TOXENV=py35-system SYSTEM_TEST=1 SKIP_APP_ENGINE_SYSTEM_TEST=1
env: TOXENV=py35-system SYSTEM_TEST=1 SKIP_APP_ENGINE_SYSTEM_TEST=1 CLOUD_SDK_ROOT=${HOME}/.cache/cloud-sdk
- python: 2.7
env: TOXENV=py27-system SYSTEM_TEST=1 SKIP_APP_ENGINE_SYSTEM_TEST=1
env: TOXENV=py27-system SYSTEM_TEST=1 SKIP_APP_ENGINE_SYSTEM_TEST=1 CLOUD_SDK_ROOT=${HOME}/.cache/cloud-sdk

This comment was marked as spam.

This comment was marked as spam.

cache:
directories:
- ${HOME}/.cache
Expand Down
9 changes: 5 additions & 4 deletions google/auth/_cloud_sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,14 @@ def get_project_id():

try:
config.read(config_file)

if config.has_section(_PROJECT_CONFIG_SECTION):
return config.get(
_PROJECT_CONFIG_SECTION, _PROJECT_CONFIG_KEY)

except configparser.Error:
return None

if config.has_section(_PROJECT_CONFIG_SECTION):
return config.get(
_PROJECT_CONFIG_SECTION, _PROJECT_CONFIG_KEY)


def load_authorized_user_credentials(info):
"""Loads an authorized user credential.
Expand Down
21 changes: 19 additions & 2 deletions system_tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,22 @@

HERE = os.path.dirname(__file__)
DATA_DIR = os.path.join(HERE, 'data')
SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, 'service_account.json')
AUTHORIZED_USER_FILE = os.path.join(DATA_DIR, 'authorized_user.json')
HTTP = urllib3.PoolManager()
TOKEN_INFO_URL = 'https://www.googleapis.com/oauth2/v3/tokeninfo'


@pytest.fixture
def service_account_file():
"""The full path to a valid service account key file."""
yield os.path.join(DATA_DIR, 'service_account.json')
yield SERVICE_ACCOUNT_FILE


@pytest.fixture
def authorized_user_file():
"""The full path to a valid authorized user file."""
yield os.path.join(DATA_DIR, 'authorized_user.json')
yield AUTHORIZED_USER_FILE


@pytest.fixture
Expand Down Expand Up @@ -67,6 +69,21 @@ def _token_info(access_token=None, id_token=None):
yield _token_info


@pytest.fixture
def verify_refresh(http_request):
"""Returns a function that verifies that credentials can be refreshed."""
def _verify_refresh(credentials):
if credentials.requires_scopes:
credentials = credentials.with_scopes(['email', 'profile'])

credentials.refresh(http_request)

assert credentials.token
assert credentials.valid

yield _verify_refresh


def verify_environment():
"""Checks to make sure that requisite data files are available."""
if not os.path.isdir(DATA_DIR):
Expand Down
195 changes: 195 additions & 0 deletions system_tests/nox.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# Copyright 2016 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Noxfile for automating system tests.

This file handles setting up environments needed by the system tests. This
separates the tests from their environment configuration.

See the `nox docs`_ for details on how this file works:

.. _nox docs: http://nox.readthedocs.io/en/latest/
"""

import os

from nox.command import Command
import py.path


HERE = os.path.dirname(__file__)
DATA_DIR = os.path.join(HERE, 'data')
SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, 'service_account.json')
AUTHORIZED_USER_FILE = os.path.join(DATA_DIR, 'authorized_user.json')
CLOUD_SDK_CONFIG_ENV = 'CLOUDSDK_CONFIG'

# If set, this is where the environment setup will store the Cloud SDK.
# If unset, it will download the SDK to a temporary directory.
CLOUD_SDK_ROOT = os.environ.get('CLOUD_SDK_ROOT')
if CLOUD_SDK_ROOT:

This comment was marked as spam.

CLOUD_SDK_ROOT = py.path.local(CLOUD_SDK_ROOT)

This comment was marked as spam.

This comment was marked as spam.

CLOUD_SDK_ROOT.ensure(dir=True)
if not CLOUD_SDK_ROOT:

This comment was marked as spam.

This comment was marked as spam.

CLOUD_SDK_ROOT = py.path.local.mkdtemp()


# Helper functions


def prerun(*args, **kwargs):
"""Runs a command before the session."""
kwargs.setdefault('silent', True)
env = os.environ.copy()
env.update(kwargs.pop('env', {}))
Command(args, env=env, **kwargs).run()


# Cloud SDK helpers


def setup_cloud_sdk():
"""Downloads and installs the Google Cloud SDK."""

# If the sdk already exists, we don't need to do anything else.
if CLOUD_SDK_ROOT.join('google-cloud-sdk').exists():

This comment was marked as spam.

This comment was marked as spam.

return

This comment was marked as spam.

This comment was marked as spam.


tar_file = 'google-cloud-sdk.tar.gz'
tar_path = CLOUD_SDK_ROOT.join(tar_file)

# Download the release.
prerun(
'wget', 'https://dl.google.com/dl/cloudsdk/release/{}'.format(

This comment was marked as spam.

This comment was marked as spam.

tar_file),
'-O', str(tar_path))

# Extract the release.
prerun('tar', 'xzf', str(tar_path), '-C', str(CLOUD_SDK_ROOT))

This comment was marked as spam.

This comment was marked as spam.

tar_path.remove()

# Run the install script.
prerun(
str(CLOUD_SDK_ROOT.join('google-cloud-sdk', 'install.sh')),

This comment was marked as spam.

This comment was marked as spam.

'--usage-reporting', 'false',
'--path-update', 'false',
'--command-completion', 'false',
env={CLOUD_SDK_CONFIG_ENV: str(CLOUD_SDK_ROOT)})

return CLOUD_SDK_ROOT


def gcloud(*args, **kwargs):
"""Calls the Cloud SDK CLI."""
prog = str(CLOUD_SDK_ROOT.join('google-cloud-sdk', 'bin', 'gcloud'))
env = {CLOUD_SDK_CONFIG_ENV: str(CLOUD_SDK_ROOT)}
return prerun(prog, *args, env=env, **kwargs)


def configure_cloud_sdk(application_default_credentials, project=False):
"""Configures the Cloud SDK with the given application default
credentials.

If project is True, then a project will be set in the active config.
If it is false, this will ensure no project is set.
"""

if project:
gcloud('config', 'set', 'project', 'example-project')
else:
gcloud('config', 'unset', 'project')

# Copy the credentials file to the config root. This is needed because
# unfortunately gcloud doesn't provide a clean way to tell it to use
# a particular set of credentials. However, this does verify that gcloud
# also considers the credentials valid by calling application-default
# print-access-token
dest = CLOUD_SDK_ROOT.join('application_default_credentials.json')
if dest.exists():
dest.remove()
py.path.local(application_default_credentials).copy(dest)

gcloud('auth', 'application-default', 'print-access-token')

This comment was marked as spam.



# Test sesssions


def session_service_account(session):
session.virtualenv = False
session.run('pytest', 'test_service_account.py')


def session_oauth2_credentials(session):
session.virtualenv = False
session.run('pytest', 'test_oauth2_credentials.py')


def session_default_explicit_service_account(session):
session.virtualenv = False
session.env['GOOGLE_APPLICATION_CREDENTIALS'] = SERVICE_ACCOUNT_FILE
session.env['EXPECT_PROJECT_ID'] = '1'

This comment was marked as spam.

This comment was marked as spam.

session.run('pytest', 'test_default.py')


def session_default_explicit_authorized_user(session):
session.virtualenv = False
session.env['GOOGLE_APPLICATION_CREDENTIALS'] = AUTHORIZED_USER_FILE
session.run('pytest', 'test_default.py')


def session_default_explicit_authorized_user_explicit_project(session):
session.virtualenv = False
session.env['GOOGLE_APPLICATION_CREDENTIALS'] = AUTHORIZED_USER_FILE
session.env['GOOGLE_CLOUD_PROJECT'] = 'example-project'
session.env['EXPECT_PROJECT_ID'] = '1'
session.run('pytest', 'test_default.py')


def session_default_cloud_sdk_service_account(session):
session.virtualenv = False
setup_cloud_sdk()
configure_cloud_sdk(SERVICE_ACCOUNT_FILE)

session.env[CLOUD_SDK_CONFIG_ENV] = str(CLOUD_SDK_ROOT)
session.env['EXPECT_PROJECT_ID'] = '1'
session.run('pytest', 'test_default.py')


def session_default_cloud_sdk_authorized_user(session):
session.virtualenv = False
setup_cloud_sdk()
configure_cloud_sdk(AUTHORIZED_USER_FILE)

session.env[CLOUD_SDK_CONFIG_ENV] = str(CLOUD_SDK_ROOT)
session.run('pytest', '--pdb', 'test_default.py')

This comment was marked as spam.

This comment was marked as spam.



def session_default_cloud_sdk_authorized_user_configured_project(session):
session.virtualenv = False
setup_cloud_sdk()
configure_cloud_sdk(AUTHORIZED_USER_FILE, project=True)

session.env[CLOUD_SDK_CONFIG_ENV] = str(CLOUD_SDK_ROOT)
session.env['EXPECT_PROJECT_ID'] = '1'
session.run('pytest', 'test_default.py')


def session_compute_engine(session):
session.virtualenv = False
session.run('pytest', 'test_compute_engine.py')


def session_app_engine(session):
session.virtualenv = False
session.run('pytest', 'app_engine/test_app_engine.py')
105 changes: 7 additions & 98 deletions system_tests/test_default.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,108 +14,17 @@

import os

import py

import google.auth
from google.auth import environment_vars
import google.oauth2.credentials
from google.oauth2 import service_account


def validate_refresh(credentials, http_request):
if credentials.requires_scopes:
credentials = credentials.with_scopes(['email', 'profile'])

credentials.refresh(http_request)

assert credentials.token
assert credentials.valid


def test_explicit_credentials_service_account(
monkeypatch, service_account_file, http_request):
monkeypatch.setitem(
os.environ, environment_vars.CREDENTIALS, service_account_file)

credentials, project_id = google.auth.default()

assert isinstance(credentials, service_account.Credentials)
assert project_id is not None

validate_refresh(credentials, http_request)


def test_explicit_credentials_authorized_user(
monkeypatch, authorized_user_file, http_request):
monkeypatch.setitem(
os.environ, environment_vars.CREDENTIALS, authorized_user_file)

credentials, project_id = google.auth.default()

assert isinstance(credentials, google.oauth2.credentials.Credentials)
assert project_id is None

validate_refresh(credentials, http_request)


def test_explicit_credentials_explicit_project_id(
monkeypatch, service_account_file, http_request):
project = 'system-test-project'
monkeypatch.setitem(
os.environ, environment_vars.CREDENTIALS, service_account_file)
monkeypatch.setitem(
os.environ, environment_vars.PROJECT, project)

_, project_id = google.auth.default()

assert project_id == project


def generate_cloud_sdk_config(
tmpdir, credentials_file, active_config='default', project=None):
tmpdir.join('active_config').write(
'{}\n'.format(active_config), ensure=True)

if project is not None:
config_file = tmpdir.join(
'configurations', 'config_{}'.format(active_config))
config_file.write(
'[core]\nproject = {}'.format(project), ensure=True)

py.path.local(credentials_file).copy(
tmpdir.join('application_default_credentials.json'))


def test_cloud_sdk_credentials_service_account(
tmpdir, monkeypatch, service_account_file, http_request):
# Create the Cloud SDK configuration tree
project = 'system-test-project'
generate_cloud_sdk_config(tmpdir, service_account_file, project=project)
monkeypatch.setitem(
os.environ, environment_vars.CLOUD_SDK_CONFIG_DIR, str(tmpdir))

credentials, project_id = google.auth.default()

assert isinstance(credentials, service_account.Credentials)
assert project_id is not None
# The project ID should be the project ID specified in the the service
# account file, not the project in the config.
assert project_id is not project

validate_refresh(credentials, http_request)

EXPECT_PROJECT_ID = os.environ.get('EXPECT_PROJECT_ID')

def test_cloud_sdk_credentials_authorized_user(
tmpdir, monkeypatch, authorized_user_file, http_request):
# Create the Cloud SDK configuration tree
project = 'system-test-project'
generate_cloud_sdk_config(tmpdir, authorized_user_file, project=project)
monkeypatch.setitem(
os.environ, environment_vars.CLOUD_SDK_CONFIG_DIR, str(tmpdir))

def test_explicit_credentials(verify_refresh):
credentials, project_id = google.auth.default()

assert isinstance(credentials, google.oauth2.credentials.Credentials)
assert project_id == project
if EXPECT_PROJECT_ID is not None:
assert project_id is not None
else:
assert project_id is None

validate_refresh(credentials, http_request)
verify_refresh(credentials)
Loading