app: Introduce API for managing setup state of the app

Useful for replacing setup_helper. This API should be considered EXPERIMENTAL
and may change.

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Sunil Mohan Adapa 2021-11-18 13:32:34 -08:00 committed by James Valleroy
parent 929e7f6dba
commit 47cb5e027f
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
2 changed files with 110 additions and 0 deletions

View File

@ -4,6 +4,8 @@ Base class for all Freedombox applications.
"""
import collections
import enum
import sys
from . import clients as clients_module
@ -40,6 +42,12 @@ class App:
_all_apps = collections.OrderedDict()
class SetupState(enum.Enum):
"""Various states of app being setup."""
NEEDS_SETUP = 'needs-setup'
NEEDS_UPDATE = 'needs-update'
UP_TO_DATE = 'up-to-date'
def __init__(self):
"""Build the app by adding components.
@ -118,6 +126,51 @@ class App:
for component in self.components.values():
component.setup(old_version=old_version)
def get_setup_state(self) -> SetupState:
"""Return whether the app is not setup or needs upgrade."""
current_version = self.get_setup_version()
if current_version and self.info.version <= current_version:
return self.SetupState.UP_TO_DATE
# If an app needs installing/updating but no setup method is available,
# then automatically set version.
#
# Minor violation of 'get' only discipline for convenience.
module = sys.modules[self.__module__]
if not hasattr(module, 'setup'):
self.set_setup_version(self.info.version)
return self.SetupState.UP_TO_DATE
if not current_version:
return self.SetupState.NEEDS_SETUP
return self.SetupState.NEEDS_UPDATE
def get_setup_version(self) -> int:
"""Return the setup version of the app."""
# XXX: Optimize version gets
from . import models
try:
app_entry = models.Module.objects.get(pk=self.app_id)
return app_entry.setup_version
except models.Module.DoesNotExist:
return 0
def needs_setup(self) -> bool:
"""Return whether the app needs to be setup.
A simple shortcut for get_setup_state() == NEEDS_SETUP
"""
return self.get_setup_state() == self.SetupState.NEEDS_SETUP
def set_setup_version(self, version: int) -> None:
"""Set the app's setup version."""
from . import models
models.Module.objects.update_or_create(
pk=self.app_id, defaults={'setup_version': version})
def enable(self):
"""Enable all the components of the app."""
for component in self.components.values():

View File

@ -4,6 +4,7 @@ Test module for App, base class for all applications.
"""
import collections
import sys
from unittest.mock import Mock, call, patch
import pytest
@ -18,6 +19,16 @@ class AppTest(App):
app_id = 'test-app'
class AppSetupTest(App):
"""Sample App for testing setup operations."""
app_id = 'test-app-setup'
def __init__(self):
super().__init__()
info = Info('test-app-setup', 3)
self.add(info)
class LeaderTest(FollowerComponent):
"""Test class for using LeaderComponent in tests."""
is_leader = True
@ -124,6 +135,52 @@ def test_app_setup(app_with_components):
component.setup.assert_has_calls([call(old_version=2)])
@pytest.mark.django_db
def test_setup_state():
"""Test retrieving the current setup state of the app."""
app = AppSetupTest()
app.info.version = 3
module = sys.modules[__name__]
app.set_setup_version(3)
assert app.get_setup_state() == App.SetupState.UP_TO_DATE
assert not app.needs_setup()
app.set_setup_version(0)
try:
delattr(module, 'setup')
except AttributeError:
pass
assert app.get_setup_state() == App.SetupState.UP_TO_DATE
assert not app.needs_setup()
assert app.get_setup_version() == 3
setattr(module, 'setup', True)
app.set_setup_version(0)
assert app.get_setup_state() == App.SetupState.NEEDS_SETUP
assert app.needs_setup()
app.set_setup_version(2)
assert app.get_setup_state() == App.SetupState.NEEDS_UPDATE
assert not app.needs_setup()
@pytest.mark.django_db
def test_get_set_setup_version():
"""Setting and getting the setup version of the app."""
app = AppSetupTest()
from plinth import models
try:
models.Module.objects.get(pk=app.app_id).delete()
except models.Module.DoesNotExist:
pass
assert app.get_setup_version() == 0
app.set_setup_version(5)
assert app.get_setup_version() == 5
def test_app_enable(app_with_components):
"""Test that enabling an app enables components."""
app_with_components.disable()