diff --git a/rsconcept/backend/apps/rsform/models/api_RSForm.py b/rsconcept/backend/apps/rsform/models/api_RSForm.py index f08d46da..0ca20588 100644 --- a/rsconcept/backend/apps/rsform/models/api_RSForm.py +++ b/rsconcept/backend/apps/rsform/models/api_RSForm.py @@ -1,12 +1,12 @@ ''' Models: RSForm API. ''' -from typing import Dict, Iterable, Optional, cast +from typing import Dict, Iterable, Optional, Union, cast from django.db import transaction from django.db.models import QuerySet from django.core.exceptions import ValidationError from cctext import Resolver, Entity, extract_entities, split_grams, TermForm -from .api_RSLanguage import get_type_prefix, generate_structure +from .api_RSLanguage import get_type_prefix, generate_structure, guess_type from .LibraryItem import LibraryItem, LibraryItemType from .Constituenta import CstType, Constituenta from .Version import Version @@ -90,18 +90,27 @@ class RSForm: return result @transaction.atomic - def insert_new(self, alias: str, insert_type: CstType, position: int = _INSERT_LAST) -> Constituenta: + def insert_new( + self, + alias: str, + cst_type: Union[CstType, None] = None, + position: int = _INSERT_LAST, + **kwargs + ) -> Constituenta: ''' Insert new constituenta at given position. All following constituents order is shifted by 1 position. ''' if self.constituents().filter(alias=alias).exists(): raise ValidationError(msg.aliasTaken(alias)) position = self._get_insert_position(position) + if cst_type is None: + cst_type = guess_type(alias) self._shift_positions(position, 1) result = Constituenta.objects.create( schema=self.item, order=position, alias=alias, - cst_type=insert_type + cst_type=cst_type, + **kwargs ) self.item.save() result.refresh_from_db() diff --git a/rsconcept/backend/apps/rsform/models/api_RSLanguage.py b/rsconcept/backend/apps/rsform/models/api_RSLanguage.py index 44060cf8..42829571 100644 --- a/rsconcept/backend/apps/rsform/models/api_RSLanguage.py +++ b/rsconcept/backend/apps/rsform/models/api_RSLanguage.py @@ -1,6 +1,6 @@ ''' Models: Definitions and utility function for RSLanguage. ''' import json -from typing import Tuple +from typing import Tuple, cast from enum import IntEnum , unique from django.db.models import TextChoices @@ -54,6 +54,14 @@ def get_type_prefix(cst_type: CstType) -> str: return 'T' return 'X' +def guess_type(alias: str) -> CstType: + ''' Get CstType for alias. ''' + prefix = alias[0] + for (value, _) in CstType.choices: + if prefix == get_type_prefix(cast(CstType, value)): + return cast(CstType, value) + return CstType.BASE + def _get_structure_prefix(alias: str, expression: str, parse: dict) -> Tuple[str, str]: ''' Generate prefix and alias for structure generation. ''' args = parse['args'] diff --git a/rsconcept/backend/apps/rsform/tests/s_views/EndpointTester.py b/rsconcept/backend/apps/rsform/tests/s_views/EndpointTester.py new file mode 100644 index 00000000..95306109 --- /dev/null +++ b/rsconcept/backend/apps/rsform/tests/s_views/EndpointTester.py @@ -0,0 +1,140 @@ +''' Utils: base tester class for endpoints. ''' +from rest_framework.test import APITestCase, APIRequestFactory, APIClient +from rest_framework import status + +from apps.users.models import User + + +def decl_endpoint(endpoint: str, method: str): + ''' Decorator for EndpointTester methods to provide API attributes. ''' + def set_endpoint_inner(function): + def wrapper(*args, **kwargs): + if '{' in endpoint: + args[0].endpoint = 'UNRESOLVED' + args[0].endpoint_mask = endpoint + else: + args[0].endpoint_mask = None + args[0].endpoint = endpoint + args[0].method = method + return function(*args, **kwargs) + return wrapper + return set_endpoint_inner + + +class EndpointTester(APITestCase): + ''' Abstract base class for Testing endpoints. ''' + def setUp(self): + self.factory = APIRequestFactory() + self.user = User.objects.create(username='UserTest') + self.client = APIClient() + self.client.force_authenticate(user=self.user) + + def toggle_staff(self, value: bool = True): + self.user.is_staff = value + self.user.save() + + def login(self): + self.client.force_authenticate(user=self.user) + + def logout(self): + self.client.logout() + + def set_params(self, **kwargs): + ''' Given named argument values resolve current endpoint_mask. ''' + if self.endpoint_mask and len(kwargs) > 0: + self.endpoint = _resolve_url(self.endpoint_mask, **kwargs) + + def get(self, endpoint: str = '', **kwargs): + if endpoint != '': + return self.client.get(endpoint) + else: + self.set_params(**kwargs) + return self.client.get(self.endpoint) + + def post(self, data=None, **kwargs): + self.set_params(**kwargs) + if not data is None: + return self.client.post(self.endpoint, data=data, format='json') + else: + return self.client.post(self.endpoint) + + def patch(self, data=None, **kwargs): + self.set_params(**kwargs) + if not data is None: + return self.client.patch(self.endpoint, data=data, format='json') + else: + return self.client.patch(self.endpoint) + + def put(self, data, **kwargs): + self.set_params(**kwargs) + return self.client.get(self.endpoint, data=data, format='json') + + def delete(self, data=None, **kwargs): + self.set_params(**kwargs) + if not data is None: + return self.client.delete(self.endpoint, data=data, format='json') + else: + return self.client.delete(self.endpoint) + + def assertOK(self, data=None, **kwargs): + response = self.execute(data, **kwargs) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def assertBadData(self, data=None, **kwargs): + response = self.execute(data, **kwargs) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def assertForbidden(self, data=None, **kwargs): + response = self.execute(data, **kwargs) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def assertNotModified(self, data=None, **kwargs): + response = self.execute(data, **kwargs) + self.assertEqual(response.status_code, status.HTTP_304_NOT_MODIFIED) + + def assertNotFound(self, data=None, **kwargs): + response = self.execute(data, **kwargs) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + def execute(self, data=None, **kwargs): + if self.method == 'get': + return self.get(**kwargs) + if self.method == 'post': + return self.post(data, **kwargs) + if self.method == 'put': + return self.put(data, **kwargs) + if self.method == 'patch': + return self.patch(data, **kwargs) + if self.method == 'delete': + return self.delete(data, **kwargs) + return None + + +def _resolve_url(url: str, **kwargs) -> str: + if url == '' or len(kwargs) == 0: + return url + pos_input: int = 0 + pos_start: int = 0 + pos_end: int = 0 + arg_names = set() + output: str = '' + while True: + pos_start = url.find('{', pos_input) + if pos_start == -1: + break + pos_end = url.find('}', pos_start) + if pos_end == -1: + break + name = url[(pos_start + 1) : pos_end] + arg_names.add(name) + if not name in kwargs: + raise KeyError(f'Missing argument: {name} | Mask: {url}') + output += url[pos_input : pos_start] + output += str(kwargs[name]) + pos_input = pos_end + 1 + if pos_input < len(url): + output += url[pos_input : len(url)] + for (key, _) in kwargs.items(): + if key not in arg_names: + raise KeyError(f'Unused argument: {name} | Mask: {url}') + return output diff --git a/rsconcept/backend/apps/rsform/tests/s_views/t_cctext.py b/rsconcept/backend/apps/rsform/tests/s_views/t_cctext.py index df22165c..796d18e5 100644 --- a/rsconcept/backend/apps/rsform/tests/s_views/t_cctext.py +++ b/rsconcept/backend/apps/rsform/tests/s_views/t_cctext.py @@ -1,63 +1,34 @@ ''' Testing views ''' -import os -import io -from zipfile import ZipFile -from rest_framework.test import APITestCase, APIRequestFactory, APIClient -from rest_framework.exceptions import ErrorDetail from rest_framework import status -from cctext import ReferenceType, split_grams +from cctext import split_grams -from apps.users.models import User -from apps.rsform.models import ( - RSForm, Constituenta, CstType, - LibraryItem, LibraryItemType, Subscription, LibraryTemplate -) -from apps.rsform.views import ( - convert_to_ascii, - convert_to_math, - parse_expression, - inflect, - parse_text, - generate_lexeme -) +from .EndpointTester import decl_endpoint, EndpointTester -class TestNaturalLanguageViews(APITestCase): - def setUp(self): - self.factory = APIRequestFactory() - self.client = APIClient() - +class TestNaturalLanguageViews(EndpointTester): + ''' Test natural language endpoints. ''' def _assert_tags(self, actual: str, expected: str): self.assertEqual(set(split_grams(actual)), set(split_grams(expected))) + @decl_endpoint(endpoint='/api/cctext/parse', method='post') def test_parse_text(self): data = {'text': 'синим слонам'} - request = self.factory.post( - '/api/cctext/parse', - data=data, format='json' - ) - response = parse_text(request) + response = self.execute(data) self.assertEqual(response.status_code, status.HTTP_200_OK) self._assert_tags(response.data['result'], 'datv,NOUN,plur,anim,masc') + @decl_endpoint(endpoint='/api/cctext/inflect', method='post') def test_inflect(self): data = {'text': 'синий слон', 'grams': 'plur,datv'} - request = self.factory.post( - '/api/cctext/inflect', - data=data, format='json' - ) - response = inflect(request) + response = self.execute(data) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['result'], 'синим слонам') + @decl_endpoint(endpoint='/api/cctext/generate-lexeme', method='post') def test_generate_lexeme(self): data = {'text': 'синий слон'} - request = self.factory.post( - '/api/cctext/generate-lexeme', - data=data, format='json' - ) - response = generate_lexeme(request) + response = self.execute(data) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data['items']), 12) self.assertEqual(response.data['items'][0]['text'], 'синий слон') diff --git a/rsconcept/backend/apps/rsform/tests/s_views/t_constituents.py b/rsconcept/backend/apps/rsform/tests/s_views/t_constituents.py index 63e0ead1..95e9b30d 100644 --- a/rsconcept/backend/apps/rsform/tests/s_views/t_constituents.py +++ b/rsconcept/backend/apps/rsform/tests/s_views/t_constituents.py @@ -1,22 +1,20 @@ ''' Testing API: Constituents. ''' -from rest_framework.test import APITestCase, APIRequestFactory, APIClient from rest_framework import status -from apps.users.models import User from apps.rsform.models import RSForm, Constituenta, CstType +from .EndpointTester import decl_endpoint, EndpointTester -class TestConstituentaAPI(APITestCase): + +class TestConstituentaAPI(EndpointTester): ''' Testing Constituenta view. ''' def setUp(self): - self.factory = APIRequestFactory() - self.user = User.objects.create(username='UserTest') - self.client = APIClient() - self.client.force_authenticate(user=self.user) + super().setUp() self.rsform_owned = RSForm.create(title='Test', alias='T1', owner=self.user) self.rsform_unowned = RSForm.create(title='Test2', alias='T2') self.cst1 = Constituenta.objects.create( alias='X1', + cst_type=CstType.BASE, schema=self.rsform_owned.item, order=1, convention='Test', @@ -25,6 +23,7 @@ class TestConstituentaAPI(APITestCase): term_forms=[{'text':'form1', 'tags':'sing,datv'}]) self.cst2 = Constituenta.objects.create( alias='X2', + cst_type=CstType.BASE, schema=self.rsform_unowned.item, order=1, convention='Test1', @@ -40,51 +39,40 @@ class TestConstituentaAPI(APITestCase): definition_raw='Test1', definition_resolved='Test2' ) + self.invalid_cst = self.cst3.pk + 1337 + @decl_endpoint('/api/constituents/{item}', method='get') def test_retrieve(self): - response = self.client.get(f'/api/constituents/{self.cst1.id}') + self.assertNotFound(item=self.invalid_cst) + response = self.execute(item=self.cst1.id) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['alias'], self.cst1.alias) self.assertEqual(response.data['convention'], self.cst1.convention) + @decl_endpoint('/api/constituents/{item}', method='patch') def test_partial_update(self): data = {'convention': 'tt'} - response = self.client.patch( - f'/api/constituents/{self.cst2.id}', - data=data, format='json' - ) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertForbidden(data, item=self.cst2.id) - self.client.logout() - response = self.client.patch( - f'/api/constituents/{self.cst1.id}', - data=data, format='json' - ) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.logout() + self.assertForbidden(data, item=self.cst1.id) - self.client.force_authenticate(user=self.user) - response = self.client.patch( - f'/api/constituents/{self.cst1.id}', - data=data, format='json' - ) + self.login() + response = self.execute(data, item=self.cst1.id) self.assertEqual(response.status_code, status.HTTP_200_OK) self.cst1.refresh_from_db() self.assertEqual(response.data['convention'], 'tt') self.assertEqual(self.cst1.convention, 'tt') - response = self.client.patch( - f'/api/constituents/{self.cst1.id}', - data=data, - format='json' - ) - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertOK(data, item=self.cst1.id) + @decl_endpoint('/api/constituents/{item}', method='patch') def test_update_resolved_no_refs(self): data = { 'term_raw': 'New term', 'definition_raw': 'New def' } - response = self.client.patch(f'/api/constituents/{self.cst3.id}', data, format='json') + response = self.execute(data, item=self.cst3.id) self.assertEqual(response.status_code, status.HTTP_200_OK) self.cst3.refresh_from_db() self.assertEqual(response.data['term_resolved'], 'New term') @@ -92,15 +80,13 @@ class TestConstituentaAPI(APITestCase): self.assertEqual(response.data['definition_resolved'], 'New def') self.assertEqual(self.cst3.definition_resolved, 'New def') + @decl_endpoint('/api/constituents/{item}', method='patch') def test_update_resolved_refs(self): data = { 'term_raw': '@{X1|nomn,sing}', 'definition_raw': '@{X1|nomn,sing} @{X1|sing,datv}' } - response = self.client.patch( - f'/api/constituents/{self.cst3.id}', - data=data, format='json' - ) + response = self.execute(data, item=self.cst3.id) self.assertEqual(response.status_code, status.HTTP_200_OK) self.cst3.refresh_from_db() self.assertEqual(self.cst3.term_resolved, self.cst1.term_resolved) @@ -108,12 +94,10 @@ class TestConstituentaAPI(APITestCase): self.assertEqual(self.cst3.definition_resolved, f'{self.cst1.term_resolved} form1') self.assertEqual(response.data['definition_resolved'], f'{self.cst1.term_resolved} form1') + @decl_endpoint('/api/constituents/{item}', method='patch') def test_readonly_cst_fields(self): data = {'alias': 'X33', 'order': 10} - response = self.client.patch( - f'/api/constituents/{self.cst1.id}', - data=data, format='json' - ) + response = self.execute(data, item=self.cst1.id) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['alias'], 'X1') self.assertEqual(response.data['alias'], self.cst1.alias) diff --git a/rsconcept/backend/apps/rsform/tests/s_views/t_library.py b/rsconcept/backend/apps/rsform/tests/s_views/t_library.py index fd36ddab..6d393845 100644 --- a/rsconcept/backend/apps/rsform/tests/s_views/t_library.py +++ b/rsconcept/backend/apps/rsform/tests/s_views/t_library.py @@ -1,26 +1,25 @@ ''' Testing API: Library. ''' -from rest_framework.test import APITestCase, APIRequestFactory, APIClient from rest_framework import status from apps.users.models import User -from apps.rsform.models import LibraryItem, LibraryItemType, Subscription, LibraryTemplate +from apps.rsform.models import LibraryItem, LibraryItemType, Subscription, LibraryTemplate, RSForm from ..utils import response_contains +from .EndpointTester import decl_endpoint, EndpointTester -class TestLibraryViewset(APITestCase): + +class TestLibraryViewset(EndpointTester): ''' Testing Library view. ''' def setUp(self): - self.factory = APIRequestFactory() - self.user = User.objects.create(username='UserTest') - self.client = APIClient() - self.client.force_authenticate(user=self.user) + super().setUp() self.owned = LibraryItem.objects.create( item_type=LibraryItemType.RSFORM, title='Test', alias='T1', owner=self.user ) + self.schema = RSForm(self.owned) self.unowned = LibraryItem.objects.create( item_type=LibraryItemType.RSFORM, title='Test2', @@ -33,93 +32,79 @@ class TestLibraryViewset(APITestCase): is_common=True ) - def test_create_anonymous(self): - self.client.logout() + @decl_endpoint('/api/library', method='post') + def test_create(self): data = {'title': 'Title'} - response = self.client.post('/api/library', data=data, format='json') - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - def test_create_populate_user(self): - data = {'title': 'Title'} - response = self.client.post('/api/library', data=data, format='json') + response = self.post(data=data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.data['title'], 'Title') self.assertEqual(response.data['owner'], self.user.id) + self.logout() + data = {'title': 'Title2'} + self.assertForbidden(data) + + @decl_endpoint('/api/library/{item}', method='patch') def test_update(self): + data = {'id': self.unowned.id, 'title': 'New title'} + self.assertForbidden(data, item=self.unowned.id) + data = {'id': self.owned.id, 'title': 'New title'} - response = self.client.patch( - f'/api/library/{self.owned.id}', - data=data, format='json' - ) + response = self.execute(data, item=self.owned.id) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['title'], 'New title') self.assertEqual(response.data['alias'], self.owned.alias) - def test_update_unowned(self): - data = {'id': self.unowned.id, 'title': 'New title'} - response = self.client.patch( - f'/api/library/{self.unowned.id}', - data=data, format='json' - ) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - + @decl_endpoint('/api/library/{item}', method='delete') def test_destroy(self): - response = self.client.delete(f'/api/library/{self.owned.id}') + response = self.execute(item=self.owned.id) self.assertTrue(response.status_code in [status.HTTP_202_ACCEPTED, status.HTTP_204_NO_CONTENT]) - def test_destroy_admin_override(self): - response = self.client.delete(f'/api/library/{self.unowned.id}') - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.user.is_staff = True - self.user.save() - response = self.client.delete(f'/api/library/{self.unowned.id}') + self.assertForbidden(item=self.unowned.id) + self.toggle_staff(True) + response = self.execute(item=self.unowned.id) self.assertTrue(response.status_code in [status.HTTP_202_ACCEPTED, status.HTTP_204_NO_CONTENT]) + @decl_endpoint('/api/library/{item}/claim', method='post') def test_claim(self): - response = self.client.post(f'/api/library/{self.owned.id}/claim') - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertForbidden(item=self.owned.id) self.owned.is_common = True self.owned.save() - response = self.client.post(f'/api/library/{self.owned.id}/claim') - self.assertEqual(response.status_code, status.HTTP_304_NOT_MODIFIED) - - response = self.client.post(f'/api/library/{self.unowned.id}/claim') - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertNotModified(item=self.owned.id) + self.assertForbidden(item=self.unowned.id) self.assertFalse(self.user in self.unowned.subscribers()) self.unowned.is_common = True self.unowned.save() - response = self.client.post(f'/api/library/{self.unowned.id}/claim') - self.assertEqual(response.status_code, status.HTTP_200_OK) + + self.assertOK(item=self.unowned.id) self.unowned.refresh_from_db() self.assertEqual(self.unowned.owner, self.user) self.assertEqual(self.unowned.owner, self.user) self.assertTrue(self.user in self.unowned.subscribers()) - def test_claim_anonymous(self): - self.client.logout() - response = self.client.post(f'/api/library/{self.owned.id}/claim') - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.logout() + self.assertForbidden(item=self.owned.id) + @decl_endpoint('/api/library/active', method='get') def test_retrieve_common(self): - self.client.logout() - response = self.client.get('/api/library/active') - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertTrue(response_contains(response, self.common)) - self.assertFalse(response_contains(response, self.unowned)) - self.assertFalse(response_contains(response, self.owned)) - - def test_retrieve_owned(self): - response = self.client.get('/api/library/active') + response = self.execute() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertTrue(response_contains(response, self.common)) self.assertFalse(response_contains(response, self.unowned)) self.assertTrue(response_contains(response, self.owned)) + self.logout() + response = self.execute() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertTrue(response_contains(response, self.common)) + self.assertFalse(response_contains(response, self.unowned)) + self.assertFalse(response_contains(response, self.owned)) + + @decl_endpoint('/api/library/active', method='get') def test_retrieve_subscribed(self): - response = self.client.get('/api/library/active') + response = self.execute() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertFalse(response_contains(response, self.unowned)) @@ -127,21 +112,23 @@ class TestLibraryViewset(APITestCase): Subscription.subscribe(user=self.user, item=self.unowned) Subscription.subscribe(user=user2, item=self.unowned) Subscription.subscribe(user=user2, item=self.owned) - response = self.client.get('/api/library/active') + + response = self.execute() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertTrue(response_contains(response, self.unowned)) self.assertEqual(len(response.data), 3) + @decl_endpoint('/api/library/{item}/subscribe', method='post') def test_subscriptions(self): response = self.client.delete(f'/api/library/{self.unowned.id}/unsubscribe') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertFalse(self.user in self.unowned.subscribers()) - response = self.client.post(f'/api/library/{self.unowned.id}/subscribe') + response = self.execute(item=self.unowned.id) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertTrue(self.user in self.unowned.subscribers()) - response = self.client.post(f'/api/library/{self.unowned.id}/subscribe') + response = self.execute(item=self.unowned.id) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertTrue(self.user in self.unowned.subscribers()) @@ -149,16 +136,40 @@ class TestLibraryViewset(APITestCase): self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertFalse(self.user in self.unowned.subscribers()) + @decl_endpoint('/api/library/templates', method='get') def test_retrieve_templates(self): - response = self.client.get('/api/library/templates') + response = self.execute() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertFalse(response_contains(response, self.common)) self.assertFalse(response_contains(response, self.unowned)) self.assertFalse(response_contains(response, self.owned)) LibraryTemplate.objects.create(lib_source=self.unowned) - response = self.client.get('/api/library/templates') + response = self.execute() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertFalse(response_contains(response, self.common)) self.assertTrue(response_contains(response, self.unowned)) self.assertFalse(response_contains(response, self.owned)) + + @decl_endpoint('/api/library/{item}/clone', method='post') + def test_clone_rsform(self): + x12 = self.schema.insert_new( + alias='X12', + term_raw = 'человек', + term_resolved = 'человек' + ) + d2 = self.schema.insert_new( + alias='D2', + term_raw = '@{X12|plur}', + term_resolved = 'люди' + ) + + data = {'title': 'Title1337'} + response = self.execute(data, item=self.owned.id) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(response.data['title'], data['title']) + self.assertEqual(response.data['items'][0]['alias'], x12.alias) + self.assertEqual(response.data['items'][0]['term_raw'], x12.term_raw) + self.assertEqual(response.data['items'][0]['term_resolved'], x12.term_resolved) + self.assertEqual(response.data['items'][1]['term_raw'], d2.term_raw) + self.assertEqual(response.data['items'][1]['term_resolved'], d2.term_resolved) diff --git a/rsconcept/backend/apps/rsform/tests/s_views/t_rsforms.py b/rsconcept/backend/apps/rsform/tests/s_views/t_rsforms.py index 0c82ea1c..c1fc0820 100644 --- a/rsconcept/backend/apps/rsform/tests/s_views/t_rsforms.py +++ b/rsconcept/backend/apps/rsform/tests/s_views/t_rsforms.py @@ -2,10 +2,8 @@ import os import io from zipfile import ZipFile -from rest_framework.test import APITestCase, APIRequestFactory, APIClient from rest_framework import status -from apps.users.models import User from apps.rsform.models import ( RSForm, Constituenta, @@ -17,44 +15,69 @@ from apps.rsform.models import ( from cctext import ReferenceType from ..utils import response_contains +from .EndpointTester import decl_endpoint, EndpointTester -class TestRSFormViewset(APITestCase): + +class TestRSFormViewset(EndpointTester): ''' Testing RSForm view. ''' def setUp(self): - self.factory = APIRequestFactory() - self.user = User.objects.create(username='UserTest') - self.client = APIClient() - self.client.force_authenticate(user=self.user) - self.owned = RSForm.create(title='Test', alias='T1', owner=self.user) + super().setUp() + self.schema = RSForm.create(title='Test', alias='T1', owner=self.user) + self.schema_id = self.schema.item.id self.unowned = RSForm.create(title='Test2', alias='T2') + self.unowned_id = self.unowned.item.id + @decl_endpoint('/api/rsforms/create-detailed', method='post') + def test_create_rsform_file(self): + work_dir = os.path.dirname(os.path.abspath(__file__)) + with open(f'{work_dir}/data/sample-rsform.trs', 'rb') as file: + data = {'file': file, 'title': 'Test123', 'comment': '123', 'alias': 'ks1'} + response = self.client.post(self.endpoint, data=data, format='multipart') + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(response.data['owner'], self.user.pk) + self.assertEqual(response.data['title'], 'Test123') + self.assertEqual(response.data['alias'], 'ks1') + self.assertEqual(response.data['comment'], '123') + + @decl_endpoint('/api/rsforms/create-detailed', method='post') + def test_create_rsform_json(self): + data = {'title': 'Test123', 'comment': '123', 'alias': 'ks1'} + response = self.execute(data) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(response.data['owner'], self.user.pk) + self.assertEqual(response.data['title'], 'Test123') + self.assertEqual(response.data['alias'], 'ks1') + self.assertEqual(response.data['comment'], '123') + + @decl_endpoint('/api/rsforms', method='get') def test_list(self): non_schema = LibraryItem.objects.create( item_type=LibraryItemType.OPERATIONS_SCHEMA, title='Test3' ) - response = self.client.get('/api/rsforms') + response = self.execute() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertFalse(response_contains(response, non_schema)) self.assertTrue(response_contains(response, self.unowned.item)) - self.assertTrue(response_contains(response, self.owned.item)) + self.assertTrue(response_contains(response, self.schema.item)) response = self.client.get('/api/library') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertTrue(response_contains(response, non_schema)) self.assertTrue(response_contains(response, self.unowned.item)) - self.assertTrue(response_contains(response, self.owned.item)) + self.assertTrue(response_contains(response, self.schema.item)) + @decl_endpoint('/api/rsforms/{item}/contents', method='get') def test_contents(self): schema = RSForm.create(title='Title1') - schema.insert_new(alias='X1', insert_type=CstType.BASE) - response = self.client.get(f'/api/rsforms/{schema.item.id}/contents') - self.assertEqual(response.status_code, status.HTTP_200_OK) + schema.insert_new('X1') + self.assertOK(item=schema.item.id) + @decl_endpoint('/api/rsforms/{item}/details', method='get') def test_details(self): schema = RSForm.create(title='Test', owner=self.user) - x1 = schema.insert_new('X1', CstType.BASE, 1) - x2 = schema.insert_new('X2', CstType.BASE, 2) + x1 = schema.insert_new('X1') + x2 = schema.insert_new('X2') x1.term_raw = 'человек' x1.term_resolved = 'человек' x2.term_raw = '@{X1|plur}' @@ -62,8 +85,7 @@ class TestRSFormViewset(APITestCase): x1.save() x2.save() - response = self.client.get(f'/api/rsforms/{schema.item.id}/details') - + response = self.execute(item=schema.item.id) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['title'], 'Test') self.assertEqual(len(response.data['items']), 2) @@ -76,14 +98,12 @@ class TestRSFormViewset(APITestCase): self.assertEqual(response.data['items'][1]['term_resolved'], x2.term_resolved) self.assertEqual(response.data['subscribers'], [self.user.pk]) + @decl_endpoint('/api/rsforms/{item}/check', method='post') def test_check(self): schema = RSForm.create(title='Test') - schema.insert_new('X1', CstType.BASE, 1) + schema.insert_new('X1') data = {'expression': 'X1=X1'} - response = self.client.post( - f'/api/rsforms/{schema.item.id}/check', - data=data, format='json' - ) + response = self.execute(data, item=schema.item.id) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['parseResult'], True) self.assertEqual(response.data['syntax'], 'math') @@ -91,22 +111,17 @@ class TestRSFormViewset(APITestCase): self.assertEqual(response.data['typification'], 'LOGIC') self.assertEqual(response.data['valueClass'], 'value') - response = self.client.post( - f'/api/rsforms/{self.unowned.item.id}/check', - data=data, format='json' - ) - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertOK(data, item=self.unowned_id) + @decl_endpoint('/api/rsforms/{item}/resolve', method='post') def test_resolve(self): schema = RSForm.create(title='Test') - x1 = schema.insert_new('X1', CstType.BASE, 1) + x1 = schema.insert_new('X1') x1.term_resolved = 'синий слон' x1.save() + data = {'text': '@{1|редкий} @{X1|plur,datv}'} - response = self.client.post( - f'/api/rsforms/{schema.item.id}/resolve', - data=data, format='json' - ) + response = self.execute(data, item=schema.item.id) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['input'], '@{1|редкий} @{X1|plur,datv}') self.assertEqual(response.data['output'], 'редким синим слонам') @@ -128,19 +143,21 @@ class TestRSFormViewset(APITestCase): self.assertEqual(response.data['refs'][1]['pos_output']['start'], 7) self.assertEqual(response.data['refs'][1]['pos_output']['finish'], 19) + @decl_endpoint('/api/rsforms/import-trs', method='post') def test_import_trs(self): work_dir = os.path.dirname(os.path.abspath(__file__)) with open(f'{work_dir}/data/sample-rsform.trs', 'rb') as file: data = {'file': file} - response = self.client.post('/api/rsforms/import-trs', data=data, format='multipart') + response = self.client.post(self.endpoint, data=data, format='multipart') self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.data['owner'], self.user.pk) self.assertTrue(response.data['title'] != '') + @decl_endpoint('/api/rsforms/{item}/export-trs', method='get') def test_export_trs(self): schema = RSForm.create(title='Test') - schema.insert_new('X1', CstType.BASE, 1) - response = self.client.get(f'/api/rsforms/{schema.item.id}/export-trs') + schema.insert_new('X1') + response = self.execute(item=schema.item.id) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.headers['Content-Disposition'], 'attachment; filename=Schema.trs') with io.BytesIO(response.content) as stream: @@ -148,31 +165,15 @@ class TestRSFormViewset(APITestCase): self.assertIsNone(zipped_file.testzip()) self.assertIn('document.json', zipped_file.namelist()) + @decl_endpoint('/api/rsforms/{item}/cst-create', method='post') def test_create_constituenta(self): - data = {'alias': 'X3', 'cst_type': 'basic'} - response = self.client.post( - f'/api/rsforms/{self.unowned.item.id}/cst-create', - data=data, format='json' - ) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + data = {'alias': 'X3', 'cst_type': CstType.BASE} + self.assertForbidden(data, item=self.unowned_id) - item = self.owned.item - Constituenta.objects.create( - schema=item, - alias='X1', - cst_type='basic', - order=1 - ) - x2 = Constituenta.objects.create( - schema=item, - alias='X2', - cst_type='basic', - order=2 - ) - response = self.client.post( - f'/api/rsforms/{item.id}/cst-create', - data=data, format='json' - ) + self.schema.insert_new('X1') + x2 = self.schema.insert_new('X2') + + response = self.execute(data, item=self.schema_id) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.data['new_cst']['alias'], 'X3') x3 = Constituenta.objects.get(alias=response.data['new_cst']['alias']) @@ -180,15 +181,12 @@ class TestRSFormViewset(APITestCase): data = { 'alias': 'X4', - 'cst_type': 'basic', + 'cst_type': CstType.BASE, 'insert_after': x2.id, 'term_raw': 'test', 'term_forms': [{'text':'form1', 'tags':'sing,datv'}] } - response = self.client.post( - f'/api/rsforms/{item.id}/cst-create', - data=data, format='json' - ) + response = self.execute(data, item=self.schema_id) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.data['new_cst']['alias'], data['alias']) x4 = Constituenta.objects.get(alias=response.data['new_cst']['alias']) @@ -196,144 +194,87 @@ class TestRSFormViewset(APITestCase): self.assertEqual(x4.term_raw, data['term_raw']) self.assertEqual(x4.term_forms, data['term_forms']) + @decl_endpoint('/api/rsforms/{item}/cst-rename', method='patch') def test_rename_constituenta(self): - cst1 = Constituenta.objects.create( + x1 = self.schema.insert_new( alias='X1', - schema=self.owned.item, - order=1, convention='Test', term_raw='Test1', term_resolved='Test1', term_forms=[{'text':'form1', 'tags':'sing,datv'}] ) - cst2 = Constituenta.objects.create( - alias='X2', - schema=self.unowned.item, - order=1 - ) - cst3 = Constituenta.objects.create( + x2_2 = self.unowned.insert_new('X2') + x3 = self.schema.insert_new( alias='X3', - schema=self.owned.item, order=2, term_raw='Test3', term_resolved='Test3', definition_raw='Test1', definition_resolved='Test2' ) - data = {'target': cst2.pk, 'alias': 'D2', 'cst_type': 'term'} - response = self.client.patch( - f'/api/rsforms/{self.unowned.item.id}/cst-rename', - data=data, format='json' - ) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + data = {'target': x2_2.pk, 'alias': 'D2', 'cst_type': CstType.TERM} + self.assertForbidden(data, item=self.unowned_id) + self.assertBadData(data, item=self.schema_id) - response = self.client.patch( - f'/api/rsforms/{self.owned.item.id}/cst-rename', - data=data, format='json' - ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + data = {'target': x1.pk, 'alias': x1.alias, 'cst_type': CstType.TERM} + self.assertBadData(data, item=self.schema_id) - data = {'target': cst1.pk, 'alias': cst1.alias, 'cst_type': 'term'} - response = self.client.patch( - f'/api/rsforms/{self.owned.item.id}/cst-rename', - data=data, format='json' - ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + data = {'target': x1.pk, 'alias': x3.alias} + self.assertBadData(data, item=self.schema_id) - data = {'target': cst1.pk, 'alias': cst3.alias} - response = self.client.patch( - f'/api/rsforms/{self.owned.item.id}/cst-rename', - data=data, format='json' + d1 = self.schema.insert_new( + alias='D1', + term_raw = '@{X1|plur}', + definition_formal = 'X1' ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(x1.order, 1) + self.assertEqual(x1.alias, 'X1') + self.assertEqual(x1.cst_type, CstType.BASE) - data = {'target': cst1.pk, 'alias': 'D2', 'cst_type': 'term'} - item = self.owned.item - d1 = Constituenta.objects.create(schema=item, alias='D1', cst_type='term', order=4) - d1.term_raw = '@{X1|plur}' - d1.definition_formal = 'X1' - d1.save() - - self.assertEqual(d1.order, 4) - self.assertEqual(cst1.order, 1) - self.assertEqual(cst1.alias, 'X1') - self.assertEqual(cst1.cst_type, CstType.BASE) - response = self.client.patch( - f'/api/rsforms/{item.id}/cst-rename', - data=data, format='json' - ) + data = {'target': x1.pk, 'alias': 'D2', 'cst_type': CstType.TERM} + response = self.execute(data, item=self.schema_id) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['new_cst']['alias'], 'D2') - self.assertEqual(response.data['new_cst']['cst_type'], 'term') + self.assertEqual(response.data['new_cst']['cst_type'], CstType.TERM) d1.refresh_from_db() - cst1.refresh_from_db() - self.assertEqual(d1.order, 4) + x1.refresh_from_db() self.assertEqual(d1.term_resolved, '') self.assertEqual(d1.term_raw, '@{D2|plur}') - self.assertEqual(cst1.order, 1) - self.assertEqual(cst1.alias, 'D2') - self.assertEqual(cst1.cst_type, CstType.TERM) + self.assertEqual(x1.order, 1) + self.assertEqual(x1.alias, 'D2') + self.assertEqual(x1.cst_type, CstType.TERM) + @decl_endpoint('/api/rsforms/{item}/cst-substitute', method='patch') def test_substitute_constituenta(self): - x1 = Constituenta.objects.create( + x1 = self.schema.insert_new( alias='X1', - schema=self.owned.item, - order=1, term_raw='Test1', term_resolved='Test1', term_forms=[{'text':'form1', 'tags':'sing,datv'}] ) - x2 = Constituenta.objects.create( + x2 = self.schema.insert_new( alias='X2', - schema=self.owned.item, - order=2, term_raw='Test2' ) - unowned = Constituenta.objects.create( - alias='X2', - schema=self.unowned.item, - order=1 - ) + unowned = self.unowned.insert_new('X2') data = {'original': x1.pk, 'substitution': unowned.pk, 'transfer_term': True} - response = self.client.patch( - f'/api/rsforms/{self.unowned.item.id}/cst-substitute', - data=data, format='json' - ) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - response = self.client.patch( - f'/api/rsforms/{self.owned.item.id}/cst-substitute', - data=data, format='json' - ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertForbidden(data, item=self.unowned_id) + self.assertBadData(data, item=self.schema_id) data = {'original': unowned.pk, 'substitution': x1.pk, 'transfer_term': True} - response = self.client.patch( - f'/api/rsforms/{self.owned.item.id}/cst-substitute', - data=data, format='json' - ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertBadData(data, item=self.schema_id) data = {'original': x1.pk, 'substitution': x1.pk, 'transfer_term': True} - response = self.client.patch( - f'/api/rsforms/{self.owned.item.id}/cst-substitute', - data=data, format='json' - ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertBadData(data, item=self.schema_id) - d1 = Constituenta.objects.create( + d1 = self.schema.insert_new( alias='D1', - schema=self.owned.item, - order=3, term_raw='@{X2|sing,datv}', definition_formal='X1' ) data = {'original': x1.pk, 'substitution': x2.pk, 'transfer_term': True} - response = self.client.patch( - f'/api/rsforms/{self.owned.item.id}/cst-substitute', - data=data, format='json' - ) + response = self.execute(data, item=self.schema_id) self.assertEqual(response.status_code, status.HTTP_200_OK) d1.refresh_from_db() @@ -342,23 +283,20 @@ class TestRSFormViewset(APITestCase): self.assertEqual(d1.term_resolved, 'form1') self.assertEqual(d1.definition_formal, 'X2') + @decl_endpoint('/api/rsforms/{item}/cst-create', method='post') def test_create_constituenta_data(self): data = { 'alias': 'X3', - 'cst_type': 'basic', + 'cst_type': CstType.BASE, 'convention': '1', 'term_raw': '2', 'definition_formal': '3', 'definition_raw': '4' } - item = self.owned.item - response = self.client.post( - f'/api/rsforms/{item.id}/cst-create', - data=data, format='json' - ) + response = self.execute(data, item=self.schema_id) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.data['new_cst']['alias'], 'X3') - self.assertEqual(response.data['new_cst']['cst_type'], 'basic') + self.assertEqual(response.data['new_cst']['cst_type'], CstType.BASE) self.assertEqual(response.data['new_cst']['convention'], '1') self.assertEqual(response.data['new_cst']['term_raw'], '2') self.assertEqual(response.data['new_cst']['term_resolved'], '2') @@ -366,79 +304,66 @@ class TestRSFormViewset(APITestCase): self.assertEqual(response.data['new_cst']['definition_raw'], '4') self.assertEqual(response.data['new_cst']['definition_resolved'], '4') + @decl_endpoint('/api/rsforms/{item}/cst-delete-multiple', method='patch') def test_delete_constituenta(self): - schema = self.owned - data = {'items': [1337]} - response = self.client.patch( - f'/api/rsforms/{schema.item.id}/cst-delete-multiple', - data=data, format='json' - ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.set_params(item=self.schema_id) + + data = {'items': [1337]} + self.assertBadData(data) + + x1 = self.schema.insert_new('X1') + x2 = self.schema.insert_new('X2') - x1 = Constituenta.objects.create(schema=schema.item, alias='X1', cst_type='basic', order=1) - x2 = Constituenta.objects.create(schema=schema.item, alias='X2', cst_type='basic', order=2) data = {'items': [x1.id]} - response = self.client.patch( - f'/api/rsforms/{schema.item.id}/cst-delete-multiple', - data=data, format='json' - ) + response = self.execute(data) x2.refresh_from_db() - schema.item.refresh_from_db() + self.schema.item.refresh_from_db() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data['items']), 1) - self.assertEqual(schema.constituents().count(), 1) + self.assertEqual(self.schema.constituents().count(), 1) self.assertEqual(x2.alias, 'X2') self.assertEqual(x2.order, 1) - x3 = Constituenta.objects.create(schema=self.unowned.item, alias='X1', cst_type='basic', order=1) + x3 = self.unowned.insert_new('X1') data = {'items': [x3.id]} - response = self.client.patch( - f'/api/rsforms/{schema.item.id}/cst-delete-multiple', - data=data, format='json' - ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertBadData(data, item=self.schema_id) + @decl_endpoint('/api/rsforms/{item}/cst-moveto', method='patch') def test_move_constituenta(self): - item = self.owned.item - data = {'items': [1337], 'move_to': 1} - response = self.client.patch( - f'/api/rsforms/{item.id}/cst-moveto', - data=data, format='json' - ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.set_params(item=self.schema_id) + + data = {'items': [1337], 'move_to': 1} + self.assertBadData(data) + + x1 = self.schema.insert_new('X1') + x2 = self.schema.insert_new('X2') - x1 = Constituenta.objects.create(schema=item, alias='X1', cst_type='basic', order=1) - x2 = Constituenta.objects.create(schema=item, alias='X2', cst_type='basic', order=2) data = {'items': [x2.id], 'move_to': 1} - response = self.client.patch( - f'/api/rsforms/{item.id}/cst-moveto', - data=data, format='json' - ) + response = self.execute(data) x1.refresh_from_db() x2.refresh_from_db() self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.data['id'], item.id) + self.assertEqual(response.data['id'], self.schema_id) self.assertEqual(x1.order, 2) self.assertEqual(x2.order, 1) - x3 = Constituenta.objects.create(schema=self.unowned.item, alias='X1', cst_type='basic', order=1) + x3 = self.unowned.insert_new('X1') data = {'items': [x3.id], 'move_to': 1} - response = self.client.patch( - f'/api/rsforms/{item.id}/cst-moveto', - data=data, format='json' - ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertBadData(data) + @decl_endpoint('/api/rsforms/{item}/reset-aliases', method='patch') def test_reset_aliases(self): - item = self.owned.item - response = self.client.patch(f'/api/rsforms/{item.id}/reset-aliases') - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.data['id'], item.id) + self.set_params(item=self.schema_id) - x2 = Constituenta.objects.create(schema=item, alias='X2', cst_type='basic', order=1) - x1 = Constituenta.objects.create(schema=item, alias='X1', cst_type='basic', order=2) - d11 = Constituenta.objects.create(schema=item, alias='D11', cst_type='term', order=3) - response = self.client.patch(f'/api/rsforms/{item.id}/reset-aliases') + response = self.execute() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['id'], self.schema_id) + + x2 = self.schema.insert_new('X2') + x1 = self.schema.insert_new('X1') + d11 = self.schema.insert_new('D11') + + response = self.execute() x1.refresh_from_db() x2.refresh_from_db() d11.refresh_from_db() @@ -450,103 +375,57 @@ class TestRSFormViewset(APITestCase): self.assertEqual(d11.order, 3) self.assertEqual(d11.alias, 'D1') - response = self.client.patch(f'/api/rsforms/{item.id}/reset-aliases') - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertOK() + @decl_endpoint('/api/rsforms/{item}/load-trs', method='patch') def test_load_trs(self): - schema = self.owned - schema.item.title = 'Test11' - schema.item.save() - x1 = Constituenta.objects.create(schema=schema.item, alias='X1', cst_type='basic', order=1) + self.set_params(item=self.schema_id) + self.schema.item.title = 'Test11' + self.schema.item.save() + x1 = self.schema.insert_new('X1') work_dir = os.path.dirname(os.path.abspath(__file__)) with open(f'{work_dir}/data/sample-rsform.trs', 'rb') as file: data = {'file': file, 'load_metadata': False} - response = self.client.patch( - f'/api/rsforms/{schema.item.id}/load-trs', - data=data, format='multipart' - ) - schema.item.refresh_from_db() + response = self.client.patch(self.endpoint, data=data, format='multipart') + self.schema.item.refresh_from_db() self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(schema.item.title, 'Test11') + self.assertEqual(self.schema.item.title, 'Test11') self.assertEqual(len(response.data['items']), 25) - self.assertEqual(schema.constituents().count(), 25) + self.assertEqual(self.schema.constituents().count(), 25) self.assertFalse(Constituenta.objects.filter(pk=x1.id).exists()) - def test_clone(self): - item = self.owned.item - item.title = 'Test11' - item.save() - x1 = Constituenta.objects.create(schema=item, alias='X12', cst_type='basic', order=1) - d1 = Constituenta.objects.create(schema=item, alias='D2', cst_type='term', order=1) - x1.term_raw = 'человек' - x1.term_resolved = 'человек' - d1.term_raw = '@{X12|plur}' - d1.term_resolved = 'люди' - x1.save() - d1.save() - - data = {'title': 'Title'} - response = self.client.post( - f'/api/library/{item.id}/clone', - data=data, format='json' - ) - - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertEqual(response.data['title'], 'Title') - self.assertEqual(response.data['items'][0]['alias'], x1.alias) - self.assertEqual(response.data['items'][0]['term_raw'], x1.term_raw) - self.assertEqual(response.data['items'][0]['term_resolved'], x1.term_resolved) - self.assertEqual(response.data['items'][1]['term_raw'], d1.term_raw) - self.assertEqual(response.data['items'][1]['term_resolved'], d1.term_resolved) - + @decl_endpoint('/api/rsforms/{item}/cst-produce-structure', method='patch') def test_produce_structure(self): - item = self.owned.item - x1 = Constituenta.objects.create(schema=item, alias='X1', cst_type='basic', order=1) - s1 = Constituenta.objects.create(schema=item, alias='S1', cst_type='structure', order=2) - s2 = Constituenta.objects.create(schema=item, alias='S2', cst_type='structure', order=3) - s3 = Constituenta.objects.create(schema=item, alias='S3', cst_type='structure', order=4) - a1 = Constituenta.objects.create(schema=item, alias='A1', cst_type='axiom', order=5) - f1 = Constituenta.objects.create(schema=item, alias='F10', cst_type='function', order=6) - invalid_id = f1.id + 1 - s1.definition_formal = 'ℬ(X1×X1)' # ℬ(X1×X1) - s2.definition_formal = 'invalid' - s3.definition_formal = 'X1×(X1×ℬℬ(X1))×ℬ(X1×X1)' - a1.definition_formal = '1=1' - f1.definition_formal = '[α∈X1, β∈X1] Fi1[{α,β}](S1)' - s1.save() - s2.save() - s3.save() - a1.save() - f1.save() - - data = {'target': invalid_id} - response = self.client.patch( - f'/api/rsforms/{item.id}/cst-produce-structure', - data=data, format='json' + self.set_params(item=self.schema_id) + x1 = self.schema.insert_new('X1') + s1 = self.schema.insert_new( + alias='S1', + definition_formal='ℬ(X1×X1)' ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - - data = {'target': x1.id} - response = self.client.patch( - f'/api/rsforms/{item.id}/cst-produce-structure', - data=data, format='json' + s2 = self.schema.insert_new( + alias='S2', + definition_formal='invalid' ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - - data = {'target': s2.id} - response = self.client.patch( - f'/api/rsforms/{item.id}/cst-produce-structure', - data=data, format='json' + s3 = self.schema.insert_new( + alias='S3', + definition_formal='X1×(X1×ℬℬ(X1))×ℬ(X1×X1)' ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - - # Testing simple structure - s1.refresh_from_db() - data = {'target': s1.id} - response = self.client.patch( - f'/api/rsforms/{item.id}/cst-produce-structure', - data=data, format='json' + a1 = self.schema.insert_new( + alias='A1', + definition_formal='1=1' ) + f1 = self.schema.insert_new( + alias='F10', + definition_formal='[α∈X1, β∈X1] Fi1[{α,β}](S1)' + ) + invalid_id = f1.pk + 1337 + + self.assertBadData({'target': invalid_id}) + self.assertBadData({'target': x1.id}) + self.assertBadData({'target': s2.id}) + + # Testing simple structure + response = self.execute({'target': s1.id}) self.assertEqual(response.status_code, status.HTTP_200_OK) result = response.data['schema'] items = [item for item in result['items'] if item['id'] in response.data['cst_list']] @@ -558,11 +437,7 @@ class TestRSFormViewset(APITestCase): # Testing complex structure s3.refresh_from_db() - data = {'target': s3.id} - response = self.client.patch( - f'/api/rsforms/{item.id}/cst-produce-structure', - data=data, format='json' - ) + response = self.execute({'target': s3.id}) self.assertEqual(response.status_code, status.HTTP_200_OK) result = response.data['schema'] items = [item for item in result['items'] if item['id'] in response.data['cst_list']] @@ -572,11 +447,7 @@ class TestRSFormViewset(APITestCase): # Testing function f1.refresh_from_db() - data = {'target': f1.id} - response = self.client.patch( - f'/api/rsforms/{item.id}/cst-produce-structure', - data=data, format='json' - ) + response = self.execute({'target': f1.id}) self.assertEqual(response.status_code, status.HTTP_200_OK) result = response.data['schema'] items = [item for item in result['items'] if item['id'] in response.data['cst_list']] diff --git a/rsconcept/backend/apps/rsform/tests/s_views/t_rslang.py b/rsconcept/backend/apps/rsform/tests/s_views/t_rslang.py index 6c30ae1c..e1d39522 100644 --- a/rsconcept/backend/apps/rsform/tests/s_views/t_rslang.py +++ b/rsconcept/backend/apps/rsform/tests/s_views/t_rslang.py @@ -1,109 +1,41 @@ ''' Testing views ''' -import os -from rest_framework.test import APITestCase, APIRequestFactory, APIClient from rest_framework.exceptions import ErrorDetail from rest_framework import status -from apps.users.models import User -from apps.rsform.models import RSForm -from apps.rsform.views import ( - convert_to_ascii, - convert_to_math, - parse_expression -) +from .EndpointTester import decl_endpoint, EndpointTester -class TestRSLanguageViews(APITestCase): - def setUp(self): - self.factory = APIRequestFactory() - self.user = User.objects.create(username='UserTest') - self.client = APIClient() - self.client.force_authenticate(user=self.user) - - def test_create_rsform(self): - work_dir = os.path.dirname(os.path.abspath(__file__)) - with open(f'{work_dir}/data/sample-rsform.trs', 'rb') as file: - data = {'file': file, 'title': 'Test123', 'comment': '123', 'alias': 'ks1'} - response = self.client.post( - '/api/rsforms/create-detailed', - data=data, format='multipart' - ) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertEqual(response.data['owner'], self.user.pk) - self.assertEqual(response.data['title'], 'Test123') - self.assertEqual(response.data['alias'], 'ks1') - self.assertEqual(response.data['comment'], '123') - - def test_create_rsform_fallback(self): - data = {'title': 'Test123', 'comment': '123', 'alias': 'ks1'} - response = self.client.post( - '/api/rsforms/create-detailed', - data=data, format='json' - ) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertEqual(response.data['owner'], self.user.pk) - self.assertEqual(response.data['title'], 'Test123') - self.assertEqual(response.data['alias'], 'ks1') - self.assertEqual(response.data['comment'], '123') - +class TestRSLanguageViews(EndpointTester): + ''' Test RS language endpoints. ''' + @decl_endpoint('/api/rslang/to-ascii', method='post') def test_convert_to_ascii(self): + data = {'data': '1=1'} + self.assertBadData(data) + data = {'expression': '1=1'} - request = self.factory.post( - '/api/rslang/to-ascii', - data=data, format='json' - ) - response = convert_to_ascii(request) + response = self.execute(data) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['result'], r'1 \eq 1') - def test_convert_to_ascii_missing_data(self): - data = {'data': '1=1'} - request = self.factory.post( - '/api/rslang/to-ascii', - data=data, format='json' - ) - response = convert_to_ascii(request) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertIsInstance(response.data['expression'][0], ErrorDetail) - + @decl_endpoint('/api/rslang/to-math', method='post') def test_convert_to_math(self): + data = {'data': r'1 \eq 1'} + self.assertBadData(data) + data = {'expression': r'1 \eq 1'} - request = self.factory.post( - '/api/rslang/to-math', - data=data, format='json' - ) - response = convert_to_math(request) + response = self.execute(data) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['result'], r'1=1') - def test_convert_to_math_missing_data(self): - data = {'data': r'1 \eq 1'} - request = self.factory.post( - '/api/rslang/to-math', - data=data, format='json' - ) - response = convert_to_math(request) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertIsInstance(response.data['expression'][0], ErrorDetail) - + @decl_endpoint('/api/rslang/parse-expression', method='post') def test_parse_expression(self): + data = {'data': r'1=1'} + self.assertBadData(data) + data = {'expression': r'1=1'} - request = self.factory.post( - '/api/rslang/parse-expression', - data=data, format='json' - ) - response = parse_expression(request) + response = self.execute(data) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['parseResult'], True) self.assertEqual(response.data['syntax'], 'math') self.assertEqual(response.data['astText'], '[=[1][1]]') - def test_parse_expression_missing_data(self): - data = {'data': r'1=1'} - request = self.factory.post( - '/api/rslang/parse-expression', - data=data, format='json' - ) - response = parse_expression(request) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertIsInstance(response.data['expression'][0], ErrorDetail) diff --git a/rsconcept/backend/apps/rsform/tests/s_views/t_versions.py b/rsconcept/backend/apps/rsform/tests/s_views/t_versions.py index 594c95ad..68cbef23 100644 --- a/rsconcept/backend/apps/rsform/tests/s_views/t_versions.py +++ b/rsconcept/backend/apps/rsform/tests/s_views/t_versions.py @@ -1,185 +1,141 @@ ''' Testing API: Versions. ''' import io +from typing import cast +from sys import version from zipfile import ZipFile -from rest_framework.test import APITestCase, APIRequestFactory, APIClient from rest_framework import status -from apps.users.models import User from apps.rsform.models import RSForm, Constituenta +from .EndpointTester import decl_endpoint, EndpointTester -class TestVersionViews(APITestCase): + +class TestVersionViews(EndpointTester): ''' Testing versioning endpoints. ''' def setUp(self): - self.factory = APIRequestFactory() - self.user = User.objects.create(username='UserTest') - self.client = APIClient() - self.client.force_authenticate(user=self.user) - self.owned = RSForm.create(title='Test', alias='T1', owner=self.user) - self.unowned = RSForm.create(title='Test2', alias='T2') + super().setUp() + self.owned = RSForm.create(title='Test', alias='T1', owner=self.user).item + self.unowned = RSForm.create(title='Test2', alias='T2').item self.x1 = Constituenta.objects.create( - schema=self.owned.item, + schema=self.owned, alias='X1', cst_type='basic', convention='testStart', order=1 ) + @decl_endpoint('/api/rsforms/{schema}/versions/create', method='post') def test_create_version(self): invalid_data = {'description': 'test'} - data = {'version': '1.0.0', 'description': 'test'} invalid_id = 1338 - response = self.client.post( - f'/api/rsforms/{invalid_id}/versions/create', - data=data, format='json' - ) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + data = {'version': '1.0.0', 'description': 'test'} - response = self.client.post( - f'/api/rsforms/{self.unowned.item.id}/versions/create', - data=data, format='json' - ) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertNotFound(data, schema=invalid_id) + self.assertForbidden(data, schema=self.unowned.id) + self.assertBadData(invalid_data, schema=self.owned.id) - response = self.client.post( - f'/api/rsforms/{self.owned.item.id}/versions/create', - data=invalid_data, format='json' - ) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - - response = self.client.post( - f'/api/rsforms/{self.owned.item.id}/versions/create', - data=data, format='json' - ) + response = self.execute(data, schema=self.owned.id) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertTrue('version' in response.data) self.assertTrue('schema' in response.data) self.assertTrue(response.data['version'] in [v['id'] for v in response.data['schema']['versions']]) - - + + @decl_endpoint('/api/rsforms/{schema}/versions/{version}', method='get') def test_retrieve_version(self): - data = {'version': '1.0.0', 'description': 'test'} - response = self.client.post( - f'/api/rsforms/{self.owned.item.id}/versions/create', - data=data, format='json' - ) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - version_id = response.data['version'] + version_id = self._create_version({'version': '1.0.0', 'description': 'test'}) + invalid_id = version_id + 1337 - invalid_id = 1338 - response = self.client.get(f'/api/rsforms/{invalid_id}/versions/{invalid_id}') - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - response = self.client.get(f'/api/rsforms/{self.owned.item.id}/versions/{invalid_id}') - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - response = self.client.get(f'/api/rsforms/{invalid_id}/versions/{version_id}') - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - response = self.client.get(f'/api/rsforms/{self.unowned.item.id}/versions/{version_id}') - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertNotFound(schema=invalid_id, version=invalid_id) + self.assertNotFound(schema=self.owned.id, version=invalid_id) + self.assertNotFound(schema=invalid_id, version=version_id) + self.assertNotFound(schema=self.unowned.id, version=version_id) - self.owned.item.alias = 'NewName' - self.owned.item.save() + self.owned.alias = 'NewName' + self.owned.save() self.x1.alias = 'X33' self.x1.save() - response = self.client.get(f'/api/rsforms/{self.owned.item.id}/versions/{version_id}') + response = self.execute(schema=self.owned.id, version=version_id) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertNotEqual(response.data['alias'], self.owned.item.alias) + self.assertNotEqual(response.data['alias'], self.owned.alias) self.assertNotEqual(response.data['items'][0]['alias'], self.x1.alias) self.assertEqual(response.data['version'], version_id) + @decl_endpoint('/api/versions/{version}', method='get') def test_access_version(self): data = {'version': '1.0.0', 'description': 'test'} - response = self.client.post( - f'/api/rsforms/{self.owned.item.id}/versions/create', - data=data, format='json' - ) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - version_id = response.data['version'] + version_id = self._create_version(data) invalid_id = version_id + 1337 - response = self.client.get(f'/api/versions/{invalid_id}') - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertNotFound(version=invalid_id) - self.client.logout() - response = self.client.get(f'/api/versions/{version_id}') + self.set_params(version=version_id) + self.logout() + response = self.execute() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['version'], data['version']) self.assertEqual(response.data['description'], data['description']) - self.assertEqual(response.data['item'], self.owned.item.id) + self.assertEqual(response.data['item'], self.owned.id) - response = self.client.patch( - f'/api/versions/{version_id}', - data=data, format='json' - ) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + data = {'version': '1.2.0', 'description': 'test1'} + self.method = 'patch' + self.assertForbidden(data) - response = self.client.delete(f'/api/versions/{version_id}') - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.method = 'delete' + self.assertForbidden() self.client.force_authenticate(user=self.user) - - data = {'version': '1.1.0', 'description': 'test1'} - response = self.client.patch( - f'/api/versions/{version_id}', - data=data, format='json' - ) - self.assertEqual(response.status_code, status.HTTP_200_OK) - - response = self.client.get(f'/api/versions/{version_id}') - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.method = 'patch' + self.assertOK(data) + response = self.get() self.assertEqual(response.data['version'], data['version']) self.assertEqual(response.data['description'], data['description']) - response = self.client.delete(f'/api/versions/{version_id}') + response = self.delete() self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - - response = self.client.get(f'/api/versions/{version_id}') + response = self.get() self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + @decl_endpoint('/api/rsforms/{schema}/versions/{version}', method='get') def test_retrieve_version_details(self): a1 = Constituenta.objects.create( - schema=self.owned.item, + schema=self.owned, alias='A1', cst_type='axiom', definition_formal='X1=X1', order=2 ) - - data = {'version': '1.0.0', 'description': 'test'} - response = self.client.post( - f'/api/rsforms/{self.owned.item.id}/versions/create', - data=data, format='json' - ) - version_id = response.data['version'] - + version_id = self._create_version({'version': '1.0.0', 'description': 'test'}) a1.definition_formal = 'X1=X2' a1.save() - response = self.client.get(f'/api/rsforms/{self.owned.item.id}/versions/{version_id}') + response = self.get(schema=self.owned.id, version=version_id) self.assertEqual(response.status_code, status.HTTP_200_OK) loaded_a1 = response.data['items'][1] self.assertEqual(loaded_a1['definition_formal'], 'X1=X1') self.assertEqual(loaded_a1['parse']['status'], 'verified') + @decl_endpoint('/api/versions/{version}/export-file', method='get') def test_export_version(self): invalid_id = 1338 - response = self.client.get(f'/api/versions/{invalid_id}/export-file') - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertNotFound(version=invalid_id) - data = {'version': '1.0.0', 'description': 'test'} - response = self.client.post( - f'/api/rsforms/{self.owned.item.id}/versions/create', - data=data, format='json' - ) - version_id = response.data['version'] - - response = self.client.get(f'/api/versions/{version_id}/export-file') + version_id = self._create_version({'version': '1.0.0', 'description': 'test'}) + response = self.get(version=version_id) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual( response.headers['Content-Disposition'], - f'attachment; filename={self.owned.item.alias}.trs' + f'attachment; filename={self.owned.alias}.trs' ) with io.BytesIO(response.content) as stream: with ZipFile(stream, 'r') as zipped_file: self.assertIsNone(zipped_file.testzip()) self.assertIn('document.json', zipped_file.namelist()) + + def _create_version(self, data) -> int: + response = self.client.post( + f'/api/rsforms/{self.owned.id}/versions/create', + data=data, format='json' + ) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + return response.data['version'] # type: ignore diff --git a/rsconcept/backend/apps/rsform/utils.py b/rsconcept/backend/apps/rsform/utils.py index c4673cb8..893e8315 100644 --- a/rsconcept/backend/apps/rsform/utils.py +++ b/rsconcept/backend/apps/rsform/utils.py @@ -64,7 +64,7 @@ def write_zipped_json(json_data: dict, json_filename: str) -> bytes: return content.getvalue() def apply_pattern(text: str, mapping: dict[str, str], pattern: re.Pattern[str]) -> str: - ''' Apply mapping to matching in regular expression patter subgroup 1 ''' + ''' Apply mapping to matching in regular expression pattern subgroup 1 ''' if text == '' or pattern == '': return text pos_input: int = 0