Skip to content

Commit f355432

Browse files
authored
Add tests for billing sample (GoogleCloudPlatform#7432)
1 parent f69e15c commit f355432

File tree

4 files changed

+149
-50
lines changed

4 files changed

+149
-50
lines changed

billing/main.py

Lines changed: 45 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -14,64 +14,59 @@
1414

1515
import base64
1616
import json
17-
import os
18-
from googleapiclient import discovery
19-
PROJECT_ID = os.getenv('GCP_PROJECT')
20-
PROJECT_NAME = f'projects/{PROJECT_ID}'
21-
def stop_billing(data, context):
22-
pubsub_data = base64.b64decode(data['data']).decode('utf-8')
23-
pubsub_json = json.loads(pubsub_data)
24-
cost_amount = pubsub_json['costAmount']
25-
budget_amount = pubsub_json['budgetAmount']
26-
if cost_amount <= budget_amount:
27-
print(f'No action necessary. (Current cost: {cost_amount})')
28-
return
2917

30-
if PROJECT_ID is None:
31-
print('No project specified with environment variable')
32-
return
18+
import google.auth
19+
from google.cloud import billing
3320

34-
billing = discovery.build(
35-
'cloudbilling',
36-
'v1',
37-
cache_discovery=False,
38-
)
3921

40-
projects = billing.projects()
22+
PROJECT_ID = google.auth.default()[1]
23+
cloud_billing_client = billing.CloudBillingClient()
24+
25+
26+
def stop_billing(data: dict, context):
27+
pubsub_data = base64.b64decode(data["data"]).decode("utf-8")
28+
pubsub_json = json.loads(pubsub_data)
29+
cost_amount = pubsub_json["costAmount"]
30+
budget_amount = pubsub_json["budgetAmount"]
31+
if cost_amount <= budget_amount:
32+
print(f"No action necessary. (Current cost: {cost_amount})")
33+
return
4134

42-
billing_enabled = __is_billing_enabled(PROJECT_NAME, projects)
35+
project_name = cloud_billing_client.common_project_path(PROJECT_ID)
36+
billing_enabled = _is_billing_enabled(project_name)
4337

4438
if billing_enabled:
45-
__disable_billing_for_project(PROJECT_NAME, projects)
39+
_disable_billing_for_project(project_name)
4640
else:
47-
print('Billing already disabled')
41+
print("Billing already disabled")
4842

4943

50-
def __is_billing_enabled(project_name, projects):
51-
"""
52-
Determine whether billing is enabled for a project
53-
@param {string} project_name Name of project to check if billing is enabled
54-
@return {bool} Whether project has billing enabled or not
55-
"""
56-
try:
57-
res = projects.getBillingInfo(name=project_name).execute()
58-
return res['billingEnabled']
59-
except KeyError:
60-
# If billingEnabled isn't part of the return, billing is not enabled
61-
return False
62-
except Exception:
63-
print('Unable to determine if billing is enabled on specified project, assuming billing is enabled')
64-
return True
65-
66-
67-
def __disable_billing_for_project(project_name, projects):
44+
def _is_billing_enabled(project_name: str) -> bool:
45+
"""Determine whether billing is enabled for a project
46+
47+
Args:
48+
project_name (str): Name of project to check if billing is enabled
49+
50+
Returns:
51+
bool: Whether project has billing enabled or not
6852
"""
69-
Disable billing for a project by removing its billing account
70-
@param {string} project_name Name of project disable billing on
53+
request = billing.GetProjectBillingInfoRequest(name=project_name)
54+
project_billing_info = cloud_billing_client.get_project_billing_info(request)
55+
56+
return project_billing_info.billing_enabled
57+
58+
59+
def _disable_billing_for_project(project_name: str) -> None:
60+
"""Disable billing for a project by removing its billing account
61+
62+
Args:
63+
project_name (str): Name of project disable billing on
7164
"""
72-
body = {'billingAccountName': ''} # Disable billing
73-
try:
74-
res = projects.updateBillingInfo(name=project_name, body=body).execute()
75-
print(f'Billing disabled: {json.dumps(res)}')
76-
except Exception:
77-
print('Failed to disable billing, possibly check permissions')
65+
request = billing.UpdateProjectBillingInfoRequest(
66+
name=project_name,
67+
project_billing_info=billing.ProjectBillingInfo(
68+
billing_account_name="" # Disable billing
69+
),
70+
)
71+
project_biling_info = cloud_billing_client.update_project_billing_info(request)
72+
print(f"Billing disabled: {project_biling_info}")

billing/requirements-test.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pytest==6.2.4

billing/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
google-cloud-billing==1.4.1

billing/test_main.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# Copyright 2022 Google LLC
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+
import base64
16+
import json
17+
from unittest import mock
18+
19+
import google.auth
20+
from google.cloud import billing
21+
22+
from main import _is_billing_enabled, stop_billing
23+
24+
25+
PROJECT_ID = google.auth.default()[1]
26+
27+
# NOTE(busunkim): These tests use mocks instead of disabling/enabling
28+
# the test project because a service account cannot be
29+
# granted sufficient permissions to add a biling account to the project.
30+
# https://cloud.google.com/billing/docs/how-to/modify-project#enable_billing_for_an_existing_project
31+
32+
33+
def test__is_billing_enabled():
34+
assert _is_billing_enabled(f"projects/{PROJECT_ID}")
35+
36+
37+
def test_stop_billing_under_budget(capsys):
38+
billing_data = {
39+
"costAmount": 10,
40+
"budgetAmount": 100.1,
41+
}
42+
43+
encoded_data = base64.b64encode(json.dumps(billing_data).encode("utf-8")).decode(
44+
"utf-8"
45+
)
46+
47+
pubsub_message = {"data": encoded_data}
48+
stop_billing(pubsub_message, None)
49+
stdout, _ = capsys.readouterr()
50+
51+
assert "No action necessary" in stdout
52+
53+
54+
def test_stop_billing_over_budget(capsys):
55+
billing_data = {
56+
"costAmount": 120,
57+
"budgetAmount": 100.1,
58+
}
59+
60+
encoded_data = base64.b64encode(json.dumps(billing_data).encode("utf-8")).decode(
61+
"utf-8"
62+
)
63+
64+
pubsub_message = {"data": encoded_data}
65+
66+
with mock.patch(
67+
"google.cloud.billing.CloudBillingClient.update_project_billing_info",
68+
autospec=True,
69+
) as update_billing:
70+
stop_billing(pubsub_message, None)
71+
update_billing.assert_called_once()
72+
73+
assert update_billing.call_args[0][1].name == f"projects/{PROJECT_ID}"
74+
assert (
75+
update_billing.call_args[0][1].project_billing_info.billing_account_name
76+
== ""
77+
)
78+
stdout, _ = capsys.readouterr()
79+
assert "Billing disabled" in stdout
80+
81+
82+
def test_stop_billing_already_disabled(capsys):
83+
billing_data = {
84+
"costAmount": 120,
85+
"budgetAmount": 100.1,
86+
}
87+
88+
encoded_data = base64.b64encode(json.dumps(billing_data).encode("utf-8")).decode(
89+
"utf-8"
90+
)
91+
92+
pubsub_message = {"data": encoded_data}
93+
94+
with mock.patch(
95+
"google.cloud.billing.CloudBillingClient.get_project_billing_info",
96+
autospec=True,
97+
return_value=billing.ProjectBillingInfo(billing_enabled=False),
98+
):
99+
stop_billing(pubsub_message, None)
100+
101+
stdout, _ = capsys.readouterr()
102+
assert "Billing already disabled" in stdout

0 commit comments

Comments
 (0)