From 56bcc4fd66d1bd349d4d9ec104cb2e10b847bc42 Mon Sep 17 00:00:00 2001 From: Roy de Goei <goei@astron.nl> Date: Mon, 23 Nov 2020 11:01:56 +0100 Subject: [PATCH] TMSS-398 Add name to Reservation model, add testdata abd testcase for Reservation and ReservationTemplate --- .../tmss/tmssapp/migrations/0001_initial.py | 13 +- .../src/tmss/tmssapp/models/specification.py | 2 +- SAS/TMSS/src/tmss/tmssapp/populate.py | 3 +- .../tmss/tmssapp/viewsets/specification.py | 2 +- .../test/t_tmssapp_specification_REST_API.py | 143 +++++++++++++++++- SAS/TMSS/test/tmss_test_data_django_models.py | 30 +++- SAS/TMSS/test/tmss_test_data_rest.py | 38 ++++- 7 files changed, 217 insertions(+), 14 deletions(-) diff --git a/SAS/TMSS/src/tmss/tmssapp/migrations/0001_initial.py b/SAS/TMSS/src/tmss/tmssapp/migrations/0001_initial.py index b8867668933..17ff8cd5c42 100644 --- a/SAS/TMSS/src/tmss/tmssapp/migrations/0001_initial.py +++ b/SAS/TMSS/src/tmss/tmssapp/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 3.0.9 on 2020-11-18 12:12 +# Generated by Django 3.0.9 on 2020-11-23 07:26 from django.conf import settings import django.contrib.postgres.fields @@ -452,10 +452,11 @@ class Migration(migrations.Migration): ('tags', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=128), blank=True, default=list, help_text='User-defined search keywords for object.', size=8)), ('created_at', models.DateTimeField(auto_now_add=True, help_text='Moment of object creation.')), ('updated_at', models.DateTimeField(auto_now=True, help_text='Moment of last object update.')), + ('name', models.CharField(help_text='Human-readable name of this object.', max_length=128)), ('description', models.CharField(help_text='Short description for this reservation, used in overviews', max_length=255)), ('start_time', models.DateTimeField(help_text='Start of this reservation.')), ('duration', models.IntegerField(help_text='Duration of this reservations.', null=True)), - ('specifications_doc', django.contrib.postgres.fields.jsonb.JSONField(help_text='Filter for selecting dataproducts from the output role.')), + ('specifications_doc', django.contrib.postgres.fields.jsonb.JSONField(help_text='Properties of this reservation')), ], options={ 'abstract': False, @@ -1238,7 +1239,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='reservation', name='project', - field=models.ForeignKey(help_text='Reservation will be accounted for this project.', on_delete=django.db.models.deletion.CASCADE, related_name='first_to_connect', to='tmssapp.Project'), + field=models.ForeignKey(help_text='Reservation will be accounted for this project.', on_delete=django.db.models.deletion.CASCADE, related_name='reserved', to='tmssapp.Project'), ), migrations.AddField( model_name='reservation', @@ -1463,10 +1464,6 @@ class Migration(migrations.Migration): model_name='sap', index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_sap_tags_7451b0_gin'), ), - migrations.AddIndex( - model_name='reservation', - index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_res_tags_0f3b14_gin'), - ), migrations.AddIndex( model_name='defaulttasktemplate', index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_def_tags_c88200_gin'), @@ -1515,4 +1512,4 @@ class Migration(migrations.Migration): model_name='dataproduct', index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='tmssapp_dat_tags_5932a3_gin'), ), - ] + ] \ No newline at end of file diff --git a/SAS/TMSS/src/tmss/tmssapp/models/specification.py b/SAS/TMSS/src/tmss/tmssapp/models/specification.py index aaa95fe2c8d..34d444a16fe 100644 --- a/SAS/TMSS/src/tmss/tmssapp/models/specification.py +++ b/SAS/TMSS/src/tmss/tmssapp/models/specification.py @@ -1105,7 +1105,7 @@ class TaskSchedulingRelationDraft(BasicCommon): super().save(force_insert, force_update, using, update_fields) -class Reservation(BasicCommon): +class Reservation(NamedCommon): project = ForeignKey('Project', related_name='reserved', on_delete=CASCADE, help_text='Reservation will be accounted for this project.') description = CharField(max_length=255, help_text='Short description for this reservation, used in overviews') start_time = DateTimeField(help_text='Start of this reservation.') diff --git a/SAS/TMSS/src/tmss/tmssapp/populate.py b/SAS/TMSS/src/tmss/tmssapp/populate.py index 45ca0000b4f..475d80f75f0 100644 --- a/SAS/TMSS/src/tmss/tmssapp/populate.py +++ b/SAS/TMSS/src/tmss/tmssapp/populate.py @@ -68,7 +68,8 @@ def populate_test_data(): # Also add a reservation object reservation_template = models.ReservationTemplate.objects.get(name="resource reservation") reservation_template_spec = get_default_json_object_for_schema(reservation_template.schema) - Reservation.objects.create(description="Just A non-scheduled reservation as example", + Reservation.objects.create(name="DummyReservation", + description="Just A non-scheduled reservation as example", project=tmss_project, specifications_template=reservation_template, specifications_doc=reservation_template_spec, diff --git a/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py b/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py index b4f85ef6b25..bb80effff4b 100644 --- a/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py +++ b/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py @@ -179,7 +179,7 @@ class DefaultReservationTemplateViewSet(LOFARViewSet): serializer_class = serializers.DefaultReservationTemplateSerializer -class ReservationTemplateViewSet(LOFARViewSet): +class ReservationTemplateViewSet(AbstractTemplateViewSet): queryset = models.ReservationTemplate.objects.all() serializer_class = serializers.ReservationTemplateSerializer diff --git a/SAS/TMSS/test/t_tmssapp_specification_REST_API.py b/SAS/TMSS/test/t_tmssapp_specification_REST_API.py index 018c985f4b6..e69a0a55f0b 100755 --- a/SAS/TMSS/test/t_tmssapp_specification_REST_API.py +++ b/SAS/TMSS/test/t_tmssapp_specification_REST_API.py @@ -267,6 +267,70 @@ class SchedulingConstraintsTemplateTestCase(unittest.TestCase): DELETE_and_assert_gone(self, url) +class ReservationTemplateTestCase(unittest.TestCase): + def test_reservation_template_list_apiformat(self): + r = requests.get(BASE_URL + '/reservation_template/?format=api', auth=AUTH) + self.assertEqual(r.status_code, 200) + self.assertTrue("Reservation Template List" in r.content.decode('utf8')) + + def test_reservation_template_GET_nonexistant_raises_error(self): + GET_and_assert_equal_expected_code(self, BASE_URL + '/reservation_template/1234321/', 404) + + def test_reservation_template_POST_and_GET(self): + # POST and GET a new item and assert correctness + test_data = test_data_creator.ReservationTemplate() + expected_data = test_data_creator.update_schema_from_template("reservationtemplate", test_data) + r_dict = POST_and_assert_expected_response(self, BASE_URL + '/reservation_template/', test_data, 201, expected_data) + url = r_dict['url'] + GET_OK_and_assert_equal_expected_response(self, url+'?format=json', expected_data) + + def test_reservation_template_PUT_invalid_raises_error(self): + test_data = test_data_creator.ReservationTemplate() + PUT_and_assert_expected_response(self, BASE_URL + '/reservation_template/9876789876/', test_data, 404, {}) + + def test_reservation_template_PUT(self): + # POST new item, verify + test_data = test_data_creator.ReservationTemplate() + expected_data = test_data_creator.update_schema_from_template("reservationtemplate", test_data) + r_dict = POST_and_assert_expected_response(self, BASE_URL + '/reservation_template/', test_data, 201, expected_data) + url = r_dict['url'] + GET_OK_and_assert_equal_expected_response(self, url, expected_data) + # PUT new values, verify + test_data2 = test_data_creator.ReservationTemplate("reservationtemplate2") + expected_data2 = test_data_creator.update_schema_from_template("reservationtemplate", test_data2) + PUT_and_assert_expected_response(self, url, test_data2, 200, expected_data2) + GET_OK_and_assert_equal_expected_response(self, url, expected_data2) + + def test_reservation_template_PATCH(self): + # POST new item, verify + test_data = test_data_creator.ReservationTemplate() + expected_data = test_data_creator.update_schema_from_template("reservationtemplate", test_data) + r_dict = POST_and_assert_expected_response(self, BASE_URL + '/reservation_template/', test_data, 201, expected_data) + url = r_dict['url'] + GET_OK_and_assert_equal_expected_response(self, url, expected_data) + + test_patch = {"name": "new_name", + "description": "better description", + "schema": minimal_json_schema(properties={"mykey": {"type":"string", "default":"my better value"}})} + + # PATCH item and verify + expected_patch_data = test_data_creator.update_schema_from_template("reservationtemplate", test_patch) + PATCH_and_assert_expected_response(self, url, test_patch, 200, expected_patch_data) + expected_data = dict(test_data) + expected_data.update(expected_patch_data) + GET_OK_and_assert_equal_expected_response(self, url, expected_data) + + def test_reservation_template_DELETE(self): + # POST new item, verify + test_data = test_data_creator.ReservationTemplate() + expected_data = test_data_creator.update_schema_from_template("reservationtemplate", test_data) + r_dict = POST_and_assert_expected_response(self, BASE_URL + '/reservation_template/', test_data, 201, expected_data) + url = r_dict['url'] + GET_OK_and_assert_equal_expected_response(self, url, expected_data) + # DELETE and check it's gone + DELETE_and_assert_gone(self, url) + + class TaskTemplateTestCase(unittest.TestCase): def test_task_template_list_apiformat(self): @@ -1099,7 +1163,6 @@ class ProjectQuotaTestCase(unittest.TestCase): GET_OK_and_assert_equal_expected_response(self, project_quota_url, project_quota_test_data) - class SchedulingSetTestCase(unittest.TestCase): def test_scheduling_set_list_apiformat(self): r = requests.get(BASE_URL + '/scheduling_set/?format=api', auth=AUTH) @@ -2661,6 +2724,84 @@ class TaskSchedulingRelationDraftTestCase(unittest.TestCase): GET_OK_and_assert_equal_expected_response(self, BASE_URL + '/task_scheduling_relation_draft/%s/' % id2, test_data_2) +class ReservationTestCase(unittest.TestCase): + def test_reservation_list_apiformat(self): + r = requests.get(BASE_URL + '/reservation/?format=api', auth=AUTH) + self.assertEqual(r.status_code, 200) + self.assertTrue("Reservation List" in r.content.decode('utf8')) + + def test_reservation_GET_nonexistant_raises_error(self): + GET_and_assert_equal_expected_code(self, BASE_URL + '/reservation/1234321/', 404) + + def test_reservation_POST_and_GET(self): + reservation_test_data = test_data_creator.Reservation(duration=60) + + # POST and GET a new item and assert correctness + r_dict = POST_and_assert_expected_response(self, BASE_URL + '/reservation/', reservation_test_data, 201, reservation_test_data) + url = r_dict['url'] + GET_OK_and_assert_equal_expected_response(self, url, reservation_test_data) + + def test_reservation_PUT_invalid_raises_error(self): + reservation_test_data = test_data_creator.Reservation(duration=60) + PUT_and_assert_expected_response(self, BASE_URL + '/reservation/9876789876/', reservation_test_data, 404, {}) + + def test_reservation_PUT(self): + project_url = test_data_creator.post_data_and_get_url(test_data_creator.Project(), '/project/') + reservation_test_data = test_data_creator.Reservation(name="reservation 1", duration=50, project_url=project_url) + + # POST new item, verify + r_dict = POST_and_assert_expected_response(self, BASE_URL + '/reservation/', reservation_test_data, 201, reservation_test_data) + url = r_dict['url'] + GET_OK_and_assert_equal_expected_response(self, url, reservation_test_data) + + reservation_test_data2 = test_data_creator.Reservation(name="reservation2", project_url=project_url) + # PUT new values, verify + PUT_and_assert_expected_response(self, url, reservation_test_data2, 200, reservation_test_data2) + GET_OK_and_assert_equal_expected_response(self, url, reservation_test_data2) + + def test_reservation_PATCH(self): + reservation_test_data = test_data_creator.Reservation(duration=60) + + # POST new item, verify + r_dict = POST_and_assert_expected_response(self, BASE_URL + '/reservation/', reservation_test_data, 201, reservation_test_data) + url = r_dict['url'] + GET_OK_and_assert_equal_expected_response(self, url, reservation_test_data) + + test_patch = {"description": "This is a new and improved description", + "duration": 90} + + # PATCH item and verify + expected_patch_data = test_data_creator.update_schema_from_template("reservationtemplate", test_patch) + PATCH_and_assert_expected_response(self, url, test_patch, 200, expected_patch_data) + expected_data = dict(reservation_test_data) + expected_data.update(test_patch) + GET_OK_and_assert_equal_expected_response(self, url, expected_patch_data) + + def test_reservation_DELETE(self): + reservation_test_data = test_data_creator.Reservation(duration=30, start_time=datetime.utcnow() + timedelta(days=1)) + # POST new item, verify + r_dict = POST_and_assert_expected_response(self, BASE_URL + '/reservation/', reservation_test_data, 201, reservation_test_data) + url = r_dict['url'] + GET_OK_and_assert_equal_expected_response(self, url, reservation_test_data) + + # DELETE and check it's gone + DELETE_and_assert_gone(self, url) + + def test_GET_Reservation_list_shows_entry(self): + test_data_1 = Reservation_test_data(duration=3600) + models.Reservation.objects.create(**test_data_1) + nbr_results = models.Reservation.objects.count() + GET_and_assert_in_expected_response_result_list(self, BASE_URL + '/reservation/', test_data_1, nbr_results) + + def test_GET_Reservation_view_returns_correct_entry(self): + test_data_1 = Reservation_test_data(name="Reservation 1", duration=60, start_time=datetime.utcnow() + timedelta(days=1)) + test_data_2 = Reservation_test_data(name="Reservation 2", duration=120, start_time=datetime.utcnow() + timedelta(days=2)) + id1 = models.Reservation.objects.create(**test_data_1).id + id2 = models.Reservation.objects.create(**test_data_2).id + GET_OK_and_assert_equal_expected_response(self, BASE_URL + '/reservation/' + str(id1) + '/', test_data_1) + GET_OK_and_assert_equal_expected_response(self, BASE_URL + '/reservation/' + str(id2) + '/', test_data_2) + + if __name__ == "__main__": logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.INFO) diff --git a/SAS/TMSS/test/tmss_test_data_django_models.py b/SAS/TMSS/test/tmss_test_data_django_models.py index f58583962a1..4c8d1845e33 100644 --- a/SAS/TMSS/test/tmss_test_data_django_models.py +++ b/SAS/TMSS/test/tmss_test_data_django_models.py @@ -68,7 +68,6 @@ def SchedulingConstraintsTemplate_test_data(name="my_SchedulingConstraintsTempla "tags": ["TMSS", "TESTING"]} - def SchedulingUnitObservingStrategyTemplate_test_data(name="my_SchedulingUnitObservingStrategyTemplate", scheduling_unit_template:models.SchedulingUnitTemplate=None, template:dict=None) -> dict: @@ -487,3 +486,32 @@ def SAPTemplate_test_data() -> dict: "schema": minimal_json_schema(), "tags": ["TMSS", "TESTING"]} + +def ReservationTemplate_test_data(name="my_ReservationTemplate", schema:dict=None) -> dict: + if schema is None: + schema = minimal_json_schema(properties={ "foo" : { "type": "string", "default": "bar" } }, required=["foo"]) + + return {"name": name, + "description": 'My ReservationTemplate description', + "schema": schema, + "tags": ["TMSS", "TESTING"]} + + +def Reservation_test_data(name="MyReservation", duration=None, start_time=None, project: models.Project = None) -> dict: + if project is None: + project = models.Project.objects.create(**Project_test_data()) + + if start_time is None: + start_time = datetime.utcnow() + timedelta(hours=12) + + specifications_template = models.ReservationTemplate.objects.create(**ReservationTemplate_test_data()) + specifications_doc = get_default_json_object_for_schema(specifications_template.schema) + + return {"name": name, + "project": project, + "description": "Test Reservation", + "tags": ["TMSS", "TESTING"], + "start_time": start_time, + "duration": duration, # can be None + "specifications_doc": specifications_doc, + "specifications_template": specifications_template} diff --git a/SAS/TMSS/test/tmss_test_data_rest.py b/SAS/TMSS/test/tmss_test_data_rest.py index 82f35cf01ae..4e7b921f9b6 100644 --- a/SAS/TMSS/test/tmss_test_data_rest.py +++ b/SAS/TMSS/test/tmss_test_data_rest.py @@ -130,6 +130,16 @@ class TMSSRESTTestDataCreator(): "tags": ["TMSS", "TESTING"]} + def ReservationTemplate(self, name="reservationtemplate1", schema:dict=None) -> dict: + if schema is None: + schema = minimal_json_schema(properties={"foo": {"type": "string", "default": "bar"}}) + + return { "name": name, + "description": 'My description', + "schema": schema, + "tags": ["TMSS", "TESTING"]} + + def SchedulingUnitObservingStrategyTemplate(self, name="my_SchedulingUnitObservingStrategyTemplate", scheduling_unit_template_url=None, template:dict=None) -> dict: @@ -671,4 +681,30 @@ class TMSSRESTTestDataCreator(): return {"specifications_doc": specifications_doc, "specifications_template": specifications_template_url, - "tags": ['tmss', 'testing']} \ No newline at end of file + "tags": ['tmss', 'testing']} + + def Reservation(self, name="My Reservation", duration=None, start_time=None, project_url=None, + specifications_template_url=None, specifications_doc=None) -> dict: + + if project_url is None: + project_url = self.post_data_and_get_url(self.Project(), '/project/') + if start_time is None: + start_time = datetime.utcnow() + timedelta(hours=12) + + if specifications_template_url is None: + specifications_template_url = self.post_data_and_get_url(self.ReservationTemplate(), '/reservation_template/') + + if specifications_doc is None: + specifications_doc = self.get_response_as_json_object(specifications_template_url + '/default') + + if isinstance(start_time, datetime): + start_time = start_time.isoformat() + + return {"name": name, + "project": project_url, + "description": "Test Reservation", + "tags": ["TMSS", "TESTING"], + "start_time": start_time, + "duration": duration, # can be None + "specifications_doc": specifications_doc, + "specifications_template": specifications_template_url} \ No newline at end of file -- GitLab