Skip to content

Commit

Permalink
Merge branch 'feature/storage-cost-breakdown' into 'develop'
Browse files Browse the repository at this point in the history
Feature/storage cost breakdown

See merge request core/sevenbridges-python!107
  • Loading branch information
Perica Prokic committed Dec 21, 2023
2 parents 18abf6a + f8384c7 commit de7755e
Show file tree
Hide file tree
Showing 10 changed files with 229 additions and 16 deletions.
10 changes: 7 additions & 3 deletions docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -699,10 +699,14 @@ The following properties are attached to each billing group:

Billing group methods
~~~~~~~~~~~~~~~~~~~~~
There is one billing group method:
Billing group methods:

``analysis_breakdown()`` fetches analysis breakdown for the selected billing group.

``storage_breakdown()`` fetches storage breakdown for the selected billing group.

``egress_breakdown()`` fetches egress breakdown for the selected billing group.

``breakdown()`` fetches a cost breakdown by project and analysis for the selected billing
group.

Manage invoices
~~~~~~~~~~~~~~~
Expand Down
2 changes: 1 addition & 1 deletion sevenbridges/models/billing_analysis_breakdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class BillingGroupAnalysisBreakdown(Resource):

@classmethod
def query(cls, bg_id, api=None, date_from=None, date_to=None,
invoice_id=None, fields=None, offset=0, limit=50):
invoice_id=None, fields=None, offset=None, limit=None):
"""
Query (List) billing group analysis breakdown. Date parameters must be
string in format MM-DD-YYYY
Expand Down
2 changes: 1 addition & 1 deletion sevenbridges/models/billing_egress_breakdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class BillingGroupEgressBreakdown(Resource):

@classmethod
def query(cls, bg_id, api=None, date_from=None, date_to=None,
invoice_id=None, fields=None, offset=0, limit=50):
invoice_id=None, fields=None, offset=None, limit=None):
"""
Query (List) billing group egress breakdown. Date parameters must be
string in format MM-DD-YYYY
Expand Down
18 changes: 9 additions & 9 deletions sevenbridges/models/billing_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,31 +55,31 @@ def query(cls, offset=None, limit=None, api=None):
)

def analysis_breakdown(self, date_from=None, date_to=None, invoice_id=None,
fields=None, offset=0, limit=50):
fields=None, offset=None, limit=None):
"""
Get Billing group analysis breakdown for the current billing group.
"""
return BillingGroupAnalysisBreakdown.query(
self.id, self._api, date_from, date_to, invoice_id, fields,
offset, limit
bg_id=self.id, api=self._api, date_from=date_from, date_to=date_to,
invoice_id=invoice_id, fields=fields, offset=offset, limit=limit
)

def storage_breakdown(self, date_from=None, date_to=None, invoice_id=None,
fields=None, offset=0, limit=50):
fields=None, offset=None, limit=None):
"""
Get Billing group storage breakdown for the current billing group.
"""
return BillingGroupStorageBreakdown.query(
self.id, self._api, date_from, date_to, invoice_id, fields,
offset, limit
bg_id=self.id, api=self._api, date_from=date_from, date_to=date_to,
invoice_id=invoice_id, fields=fields, offset=offset, limit=limit
)

def egress_breakdown(self, date_from=None, date_to=None, invoice_id=None,
fields=None, offset=0, limit=50):
fields=None, offset=None, limit=None):
"""
Get Billing group egress breakdown for the current billing group.
"""
return BillingGroupEgressBreakdown.query(
self.id, self._api, date_from, date_to, invoice_id, fields,
offset, limit
bg_id=self.id, api=self._api, date_from=date_from, date_to=date_to,
invoice_id=invoice_id, fields=fields, offset=offset, limit=limit
)
2 changes: 1 addition & 1 deletion sevenbridges/models/billing_storage_breakdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class BillingGroupStorageBreakdown(Resource):

@classmethod
def query(cls, bg_id, api=None, date_from=None, date_to=None,
invoice_id=None, fields=None, offset=0, limit=50):
invoice_id=None, fields=None, offset=None, limit=None):
"""
Query (List) billing group storage breakdown. Date parameters must be
string in format MM-DD-YYYY
Expand Down
18 changes: 17 additions & 1 deletion sevenbridges/models/compound/measurement.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
from sevenbridges.meta.resource import Resource
from sevenbridges.meta.fields import StringField, FloatField
from sevenbridges.meta.fields import StringField, FloatField, CompoundField


class StorageCost(Resource):
"""
Storage cost resource contains breakdown information regarding the amount
of cost and the currency used.
"""
amount = FloatField(read_only=True)
currency = StringField(read_only=True)

def __str__(self):
return (
f'<Storage cost breakdown amount={self.amount}, '
f'currency={self.currency}>'
)


class Measurement(Resource):
Expand All @@ -9,6 +24,7 @@ class Measurement(Resource):
"""
size = FloatField(read_only=True)
unit = StringField(read_only=True)
cost = CompoundField(StorageCost, read_only=True)

def __str__(self):
return f'<Measurement size={self.size}, unit={self.unit}>'
12 changes: 12 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ def __init__(self, request_mocker, base_url):
self.drs_imports = providers.DRSImportProvider(
request_mocker, base_url
)
self.billing_group = providers.BillingGroupProvider(
request_mocker, base_url
)
self.billing_group_storage_breakdown = (
providers.BillingGroupStorageBreakdownProvider(
request_mocker, base_url
)
)


class Verifier:
Expand Down Expand Up @@ -97,6 +105,10 @@ def __init__(self, request_mocker):
request_mocker
)
self.drs_imports = verifiers.DRSImportsVerifier(request_mocker)
self.billing_group = verifiers.BillingGroupVerifier(request_mocker)
self.billing_group_storage_breakdown = (
verifiers.BillingGroupStorageBreakdownVerifier(request_mocker)
)


@pytest.fixture
Expand Down
85 changes: 85 additions & 0 deletions tests/providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1948,3 +1948,88 @@ def can_be_submitted_in_bulk(self, imports_data):
for _ in range(len(imports_data))
]
self.request_mocker.post('/bulk/drs/imports/create', json=data)


class BillingGroupProvider:
def __init__(self, request_mocker, base_url):
self.request_mocker = request_mocker
self.base_url = base_url

@staticmethod
def default() -> dict:
return {
'href': generator.url(),
'id': generator.uuid4(),
'name': generator.name(),
'owner': generator.user_name(),
'type': generator.name(),
'pending': False,
'disabled': False,
'balance': float(generator.pydecimal(positive=True))
}

def exist(self, num_of_bg):
items = [BillingGroupProvider.default() for _ in range(num_of_bg)]

url = '/billing/groups'
href = self.base_url + url
response = {
'href': href,
'items': items,
'links': []
}
self.request_mocker.get(
href,
json=response,
headers={'x-total-matching-query': str(num_of_bg)}
)


class BillingGroupStorageBreakdownProvider:
def __init__(self, request_mocker, base_url):
self.request_mocker = request_mocker
self.base_url = base_url

@staticmethod
def default() -> dict:
return {
'project_name': generator.name(),
'project_created_by': generator.user_name(),
'location': generator.name(),
'active': None,
'archived': None,
'project_locked': False
}

@staticmethod
def generate_storage_breakdown() -> dict:
return {
'size': str(generator.pydecimal(positive=True)),
'unit': 'GB/Month',
'cost': {
'currency': 'USD',
'amount': str(generator.pydecimal(positive=True)),
}
}

def exist(self, bg_id, num_of_objects, with_cost=False):
items = []
for _ in range(num_of_objects):
item = BillingGroupStorageBreakdownProvider.default()
if with_cost:
item['archived'] = self.generate_storage_breakdown()
item['active'] = self.generate_storage_breakdown()
items.append(item)

url = f'/billing/groups/{bg_id}/breakdown/storage'
href = self.base_url + url
response = {
'href': href,
'items': items,
'links': []
}
self.request_mocker.get(
href,
json=response,
headers={'x-total-matching-query': str(num_of_objects)}
)
58 changes: 58 additions & 0 deletions tests/test_billing_groups.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import pytest
import faker

generator = faker.Factory.create()


def test_billing_group_query(api, given, verifier):
# preconditions
total = 10
given.billing_group.exist(total)

# action
billing_groups = api.billing_groups.query()

# verification
assert billing_groups.total == total
assert len(billing_groups) == total

verifier.billing_group.groups_fetched()


@pytest.mark.parametrize('with_cost', [True, False])
def test_billing_storage_breakdown(api, given, verifier, with_cost):
# precondition
given.billing_group.exist(1)

# action
billing_groups = api.billing_groups.query()
billing_group = billing_groups[0]

# precondition
total = 5
given.billing_group_storage_breakdown.exist(
bg_id=billing_group.id,
num_of_objects=total,
with_cost=with_cost
)

# action
storage_breakdown = billing_group.storage_breakdown()

# verification
verifier.billing_group.groups_fetched()
verifier.billing_group_storage_breakdown.fetched(
billing_group=billing_group
)

verifier.billing_group_storage_breakdown.fetched(
billing_group=billing_group
)
assert len(storage_breakdown) == total
for breakdown in storage_breakdown:
if with_cost:
assert breakdown.active is not None
assert breakdown.archived is not None
else:
assert breakdown.active is None
assert breakdown.archived is None
38 changes: 38 additions & 0 deletions tests/verifiers.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from copy import deepcopy


# noinspection PyProtectedMember
class Assert:
def __init__(self, request_mocker):
Expand Down Expand Up @@ -615,3 +618,38 @@ def bulk_retrieved(self, _id):

def bulk_submitted(self):
self.checker.check_url('/bulk/drs/imports/create')


class BillingGroupVerifier:
DEFAULT_QS = {'fields': ['_all']}

def __init__(self, request_mocker):
self.request_mocker = request_mocker
self.checker = Assert(self.request_mocker)

def groups_fetched(self, offset=None, limit=None):
qs = deepcopy(self.DEFAULT_QS)
if limit is not None:
qs['limit'] = limit
if offset is not None:
qs['offset'] = offset
self.checker.check_url('/billing/groups')
self.checker.check_query(qs)


class BillingGroupStorageBreakdownVerifier:

def __init__(self, request_mocker):
self.request_mocker = request_mocker
self.checker = Assert(self.request_mocker)

def fetched(self, billing_group, offset=None, limit=None):
qs = {}
if limit is not None:
qs['limit'] = limit
if offset is not None:
qs['offset'] = offset
self.checker.check_url(
f'/billing/groups/{billing_group.id}/breakdown/storage'
)
self.checker.check_query(qs)

0 comments on commit de7755e

Please sign in to comment.