diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml index b5e2e624..17a33f08 100644 --- a/.github/workflows/unittest.yml +++ b/.github/workflows/unittest.yml @@ -46,7 +46,7 @@ jobs: fail_below_min: true format: markdown hide_branch_rate: false - hide_complexity: false + hide_complexity: true indicators: true output: both thresholds: '15 30' diff --git a/mod/controller/rest/snapshot.py b/mod/controller/rest/snapshot.py index f322de62..ea801676 100644 --- a/mod/controller/rest/snapshot.py +++ b/mod/controller/rest/snapshot.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 # SPDX-FileCopyrightText: 2012-2023 MOD Audio UG # SPDX-License-Identifier: AGPL-3.0-or-later +from tornado import gen, web + from mod.controller.handler.json_request_handler import JsonRequestHandler from mod.session import SESSION from mod.settings import DEFAULT_SNAPSHOT_NAME @@ -66,3 +68,34 @@ def post(self): """ ok = SESSION.host.snapshot_save() self.write(ok) + +class SnapshotSaveAs(JsonRequestHandler): + + # TODO: Replace GET /snapshot/saveas + # to POST /pedalboards/current/snapshots/saveas + @web.asynchronous + @gen.engine + def get(self): + """ + Create a new snapshot with the suggested ``title`` based on the current pedalboard status; + .. code-block:: json + + { + "ok": true, + "id": 1, + "title": "Snapshot name" + } + + :return: `true` if it was successfully deleted + """ + title = self.get_argument('title') + idx = SESSION.host.snapshot_saveas(title) + title = SESSION.host.snapshot_name(idx) + + yield gen.Task(SESSION.host.hmi_report_ss_name_if_current, idx) + + self.write({ + 'ok': idx is not None, # FIXME: Always true + 'id': idx, + 'title': title, + }) diff --git a/mod/webserver.py b/mod/webserver.py index ac674f50..050c56d1 100644 --- a/mod/webserver.py +++ b/mod/webserver.py @@ -22,7 +22,7 @@ from mod.controller.handler.json_request_handler import JsonRequestHandler from mod.controller.handler.timeless_request_handler import TimelessRequestHandler -from mod.controller.rest.snapshot import SnapshotName, SnapshotList, SnapshotSave +from mod.controller.rest.snapshot import SnapshotName, SnapshotList, SnapshotSave, SnapshotSaveAs try: from signal import signal, SIGUSR1, SIGUSR2 @@ -1555,21 +1555,6 @@ def post(self, mode): ok = yield gen.Task(SESSION.web_set_sync_mode, transport_sync) self.write(ok) -class SnapshotSaveAs(JsonRequestHandler): - @web.asynchronous - @gen.engine - def get(self): - title = self.get_argument('title') - idx = SESSION.host.snapshot_saveas(title) - title = SESSION.host.snapshot_name(idx) - - yield gen.Task(SESSION.host.hmi_report_ss_name_if_current, idx) - - self.write({ - 'ok': idx is not None, - 'id': idx, - 'title': title, - }) class SnapshotRename(JsonRequestHandler): @web.asynchronous diff --git a/test/controller/snapshot_save_as_test.py b/test/controller/snapshot_save_as_test.py new file mode 100644 index 00000000..cd37eaae --- /dev/null +++ b/test/controller/snapshot_save_as_test.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: 2012-2023 MOD Audio UG +# SPDX-License-Identifier: AGPL-3.0-or-later +# This test uses coroutine style. +import json +from uuid import uuid4 + +from tornado.httpclient import HTTPRequest +from tornado.testing import AsyncHTTPTestCase + +from mod.webserver import application + + +class SnapshotSaveAsTestCase(AsyncHTTPTestCase): + def get_app(self): + return application + + def test_save_as(self): + # Create a snapshot + name = str(uuid4()) + snapshot = self._save_as(name) + + # Assert is saved + self.assertDictEqual( + { + "ok": True, + "id": snapshot['id'], + "title": name + }, + snapshot + ) + + # Clear + self.fetch("/snapshot/remove?id=" + str(snapshot['id'])) + + def test_save_as_deleted_snapshot(self): + """ + It's possible, because the snapshot is created based on pedalboard's + current state instead of a last snapshot loaded + """ + # Create two snapshots + # because it is necessary to exists at least one + snapshot_1_name = str(uuid4()) + snapshot_2_name = str(uuid4()) + + snapshot_1 = self._save_as(snapshot_1_name) + snapshot_2 = self._save_as(snapshot_2_name) + + # Save snapshot created + self.fetch("/snapshot/load?id=" + str(snapshot_2['id'])) + response = self.post("/snapshot/save") + + # Assert is saved + self.assertTrue(json.loads(response.body)) + + # Delete created snapshot + self.fetch("/snapshot/remove?id=" + str(snapshot_2['id'])) + + # Save as deleted snapshot + snapshot_3_name = str(uuid4()) + snapshot_3 = self._save_as(snapshot_3_name) + self.assertEqual(response.code, 200) + self.assertDictEqual( + { + "ok": True, + "id": snapshot_3['id'], + "title": snapshot_3_name + }, + snapshot_3 + ) + + # Clear + self.fetch("/snapshot/remove?id=" + str(snapshot_3['id'])) + self.fetch("/snapshot/remove?id=" + str(snapshot_1['id'])) + + def test_save_duplicated_name(self): + snapshot_1_name = snapshot_2_name = str(uuid4()) + + self.assertEqual(snapshot_1_name, snapshot_2_name) + + snapshot_1 = self._save_as(snapshot_1_name) + snapshot_2 = self._save_as(snapshot_2_name) + + self.assertNotEqual(snapshot_1['title'], snapshot_2['title']) + self.assertTrue(snapshot_1['title'] < snapshot_2['title']) + + # Clear + self.fetch("/snapshot/remove?id=" + str(snapshot_2['id'])) + self.fetch("/snapshot/remove?id=" + str(snapshot_1['id'])) + + def post(self, url): + self.http_client.fetch(HTTPRequest(self.get_url(url), "POST", allow_nonstandard_methods=True), self.stop) + return self.wait() + + def _save_as(self, name): + response = self.fetch("/snapshot/saveas?title=" + name) + self.assertEqual(response.code, 200) + + return json.loads(response.body)