Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
10 changes: 9 additions & 1 deletion press/api/site.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,14 +297,22 @@ def _validate_warranty_change(
"""Throw if a warranty-affecting plan change is not allowed at this time."""
if is_new or is_system_user or not is_current_dedicated_server_plan:
return
if is_current_plan_supported == is_product_warranty_enabled_for_plan_(new_plan):
is_new_plan_supported = is_product_warranty_enabled_for_plan_(new_plan)
if is_current_plan_supported == is_new_plan_supported:
return

next_warranty_change = get_next_allowed_dedicated_product_warranty_change_date(site)
if get_datetime() < next_warranty_change:
pretty_date = format_datetime(next_warranty_change, "MMM d, YYYY hh:mm a")
frappe.throw(f"Cannot change product warranty for this site before {pretty_date}") # nosemgrep
<<<<<<< HEAD

=======
# The quota check only gates enabling warranty (which consumes a slot);
# disabling is always allowed once the cooldown above has passed.
if not is_new_plan_supported:
return
>>>>>>> 518365fba (fix(product-warranty-switch): Gate warranty enabling only)
quota = get_available_warranty_quota_for_server(server)
if quota.get("available") <= 0:
frappe.throw(
Expand Down
68 changes: 68 additions & 0 deletions press/api/tests/test_site.py
Original file line number Diff line number Diff line change
Expand Up @@ -1128,3 +1128,71 @@ def test_add_domain_creates_site_domain_when_dns_matches(self):
):
add_domain(site.name, "example.com")
self.assertTrue(frappe.db.exists("Site Domain", {"site": site.name, "domain": "example.com"}))


class TestCheckWarrantyRestrictions(FrappeTestCase):
"""Tests for _check_warranty_restrictions.

The cooldown applies to both enabling and disabling. The server quota,
however, only gates enabling (which consumes a slot) — disabling is
always allowed once the cooldown has passed.
"""

def _check(self, *, current_supported, new_supported, quota_available=0, cooldown_active=False):
from press.api.site import _check_warranty_restrictions

now = datetime.datetime.now()
next_change = now + datetime.timedelta(days=1) if cooldown_active else now
with (
patch(
"press.api.site.is_product_warranty_enabled_for_plan_",
return_value=new_supported,
),
patch(
"press.api.site.get_next_allowed_dedicated_product_warranty_change_date",
return_value=next_change,
),
patch(
"press.api.site.get_available_warranty_quota_for_server",
return_value={"available": quota_available},
),
):
_check_warranty_restrictions(
site="test-site.frappe.cloud",
server="test-server",
new_plan="test-plan",
is_new=False,
is_system_user=False,
is_current_dedicated_server_plan=True,
is_current_plan_supported=current_supported,
)

def test_disabling_warranty_allowed_even_when_quota_exhausted(self):
"""Disabling support on a server with no quota left must not throw."""
self._check(current_supported=True, new_supported=False, quota_available=0)

def test_enabling_warranty_blocked_when_quota_exhausted(self):
"""Enabling support on a server with no quota left must throw."""
self.assertRaisesRegex(
frappe.ValidationError,
"exhausted the site warranty quota",
self._check,
current_supported=False,
new_supported=True,
quota_available=0,
)

def test_enabling_warranty_allowed_when_quota_available(self):
"""Enabling support with quota left must not throw."""
self._check(current_supported=False, new_supported=True, quota_available=1)

def test_disabling_warranty_blocked_during_cooldown(self):
"""Disabling support is still subject to the cooldown window."""
self.assertRaisesRegex(
frappe.ValidationError,
"Cannot change product warranty for this site before",
self._check,
current_supported=True,
new_supported=False,
cooldown_active=True,
)
Loading