test(api): API Permission ViewSet Abstract Class added

ref: #15 #248 #345
This commit is contained in:
2024-10-14 21:53:34 +09:30
parent 5edbdd76f4
commit 2690e17f93
8 changed files with 496 additions and 74 deletions

View File

@ -0,0 +1,470 @@
import pytest
import unittest
from django.shortcuts import reverse
from django.test import TestCase, Client
class APIPermissionView:
model: object
""" Item Model to test """
app_namespace: str = None
""" URL namespace """
url_name: str
""" URL name of the view to test """
url_view_kwargs: dict = None
""" URL kwargs of the item page """
def test_view_user_anon_denied(self):
""" Check correct permission for view
Attempt to view as anon user
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
response = client.get(url)
assert response.status_code == 401
def test_view_no_permission_denied(self):
""" Check correct permission for view
Attempt to view with user missing permission
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.no_permissions_user)
response = client.get(url)
assert response.status_code == 403
def test_view_different_organizaiton_denied(self):
""" Check correct permission for view
Attempt to view with user from different organization
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.different_organization_user)
response = client.get(url)
assert response.status_code == 403
def test_view_has_permission(self):
""" Check correct permission for view
Attempt to view as user with view permission
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.view_user)
response = client.get(url)
assert response.status_code == 200
class APIPermissionAdd:
model: object
""" Item Model to test """
app_namespace: str = None
""" URL namespace """
url_list: str
""" URL view name of the item list page """
url_kwargs: dict = None
""" URL view kwargs for the item list page """
add_data: dict = None
def test_add_user_anon_denied(self):
""" Check correct permission for add
Attempt to add as anon user
"""
client = Client()
if self.url_kwargs:
url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-list')
response = client.put(url, data=self.add_data)
assert response.status_code == 401
# @pytest.mark.skip(reason="ToDO: figure out why fails")
def test_add_no_permission_denied(self):
""" Check correct permission for add
Attempt to add as user with no permissions
"""
client = Client()
if self.url_kwargs:
url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-list')
client.force_login(self.no_permissions_user)
response = client.post(url, data=self.add_data)
assert response.status_code == 403
# @pytest.mark.skip(reason="ToDO: figure out why fails")
def test_add_different_organization_denied(self):
""" Check correct permission for add
attempt to add as user from different organization
"""
client = Client()
if self.url_kwargs:
url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-list')
client.force_login(self.different_organization_user)
response = client.post(url, data=self.add_data)
assert response.status_code == 403
def test_add_permission_view_denied(self):
""" Check correct permission for add
Attempt to add a user with view permission
"""
client = Client()
if self.url_kwargs:
url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-list')
client.force_login(self.view_user)
response = client.post(url, data=self.add_data)
assert response.status_code == 403
def test_add_has_permission(self):
""" Check correct permission for add
Attempt to add as user with permission
"""
client = Client()
if self.url_kwargs:
url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-list')
client.force_login(self.add_user)
response = client.post(url, data=self.add_data)
assert response.status_code == 201
class APIPermissionChange:
model: object
""" Item Model to test """
app_namespace: str = None
""" URL namespace """
url_name: str
""" URL name of the view to test """
url_view_kwargs: dict = None
""" URL kwargs of the item page """
change_data: dict = None
def test_change_user_anon_denied(self):
""" Check correct permission for change
Attempt to change as anon
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
response = client.patch(url, data=self.change_data, content_type='application/json')
assert response.status_code == 401
def test_change_no_permission_denied(self):
""" Ensure permission view cant make change
Attempt to make change as user without permissions
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.no_permissions_user)
response = client.patch(url, data=self.change_data, content_type='application/json')
assert response.status_code == 403
def test_change_different_organization_denied(self):
""" Ensure permission view cant make change
Attempt to make change as user from different organization
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.different_organization_user)
response = client.patch(url, data=self.change_data, content_type='application/json')
assert response.status_code == 403
def test_change_permission_view_denied(self):
""" Ensure permission view cant make change
Attempt to make change as user with view permission
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.view_user)
response = client.patch(url, data=self.change_data, content_type='application/json')
assert response.status_code == 403
def test_change_permission_add_denied(self):
""" Ensure permission view cant make change
Attempt to make change as user with add permission
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.add_user)
response = client.patch(url, data=self.change_data, content_type='application/json')
assert response.status_code == 403
def test_change_has_permission(self):
""" Check correct permission for change
Make change with user who has change permission
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.change_user)
response = client.patch(url, data=self.change_data, content_type='application/json')
assert response.status_code == 200
class APIPermissionDelete:
model: object
""" Item Model to test """
app_namespace: str = None
""" URL namespace """
url_name: str
""" URL name of the view to test """
url_view_kwargs: dict = None
""" URL kwargs of the item page """
delete_data: dict = None
def test_delete_user_anon_denied(self):
""" Check correct permission for delete
Attempt to delete item as anon user
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
response = client.delete(url, data=self.delete_data)
assert response.status_code == 401
def test_delete_no_permission_denied(self):
""" Check correct permission for delete
Attempt to delete as user with no permissons
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.no_permissions_user)
response = client.delete(url, data=self.delete_data)
assert response.status_code == 403
def test_delete_different_organization_denied(self):
""" Check correct permission for delete
Attempt to delete as user from different organization
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.different_organization_user)
response = client.delete(url, data=self.delete_data)
assert response.status_code == 403
def test_delete_permission_view_denied(self):
""" Check correct permission for delete
Attempt to delete as user with veiw permission only
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.view_user)
response = client.delete(url, data=self.delete_data)
assert response.status_code == 403
def test_delete_permission_add_denied(self):
""" Check correct permission for delete
Attempt to delete as user with add permission only
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.add_user)
response = client.delete(url, data=self.delete_data)
assert response.status_code == 403
def test_delete_permission_change_denied(self):
""" Check correct permission for delete
Attempt to delete as user with change permission only
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.change_user)
response = client.delete(url, data=self.delete_data)
assert response.status_code == 403
def test_delete_has_permission(self):
""" Check correct permission for delete
Delete item as user with delete permission
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.delete_user)
response = client.delete(url, data=self.delete_data)
assert response.status_code == 204
class APIPermissions(
APIPermissionAdd,
APIPermissionChange,
APIPermissionDelete,
APIPermissionView
):
""" Abstract class containing all API Permission test cases """
model: object
""" Item Model to test """

View File

@ -1,11 +0,0 @@
---
title: API Model Permission add Test Cases
description: No Fuss Computings model permissions add unit tests
date: 2024-06-16
template: project.html
about: https://gitlab.com/nofusscomputing/infrastructure/configuration-management/centurion_erp
---
::: app.api.tests.abstract.api_permissions.APIPermissionAdd
options:
show_source: true

View File

@ -1,11 +0,0 @@
---
title: Model Permissions Change Test Cases
description: No Fuss Computings model permissions change unit tests
date: 2024-06-15
template: project.html
about: https://gitlab.com/nofusscomputing/infrastructure/configuration-management/centurion_erp
---
::: app.api.tests.abstract.api_permissions.APIPermissionChange
options:
show_source: true

View File

@ -1,11 +0,0 @@
---
title: Model Permissions Delete Test Cases
description: No Fuss Computings model permissions delete unit tests
date: 2024-06-15
template: project.html
about: https://gitlab.com/nofusscomputing/infrastructure/configuration-management/centurion_erp
---
::: app.api.tests.abstract.api_permissions.APIPermissionDelete
options:
show_source: true

View File

@ -1,11 +0,0 @@
---
title: Model Permissions View Test Cases
description: No Fuss Computings model permissions view unit tests
date: 2024-06-15
template: project.html
about: https://gitlab.com/nofusscomputing/infrastructure/configuration-management/centurion_erp
---
::: app.api.tests.abstract.api_permissions.APIPermissionView
options:
show_source: true

View File

@ -1,12 +0,0 @@
---
title: API Model Permissions Test Cases
description: No Fuss Computings model permissions add unit tests
date: 2024-06-16
template: project.html
about: https://gitlab.com/nofusscomputing/infrastructure/configuration-management/centurion_erp
---
::: app.api.tests.abstract.api_permissions.APIPermissions
options:
show_source: true
inherited_members: true

View File

@ -10,14 +10,36 @@ Unit tests are written to aid in application stability and to assist in preventi
User Interface (UI) test are written _if applicable_ to test the user interface to ensure that it functions as it should. Changes to the UI will need to be tested. User Interface (UI) test are written _if applicable_ to test the user interface to ensure that it functions as it should. Changes to the UI will need to be tested.
!!! note
As of release v1.3, the UI has moved to it's [own project](https://github.com/nofusscomputing/centurion_erp_ui) with the current Django UI feature locked and depreciated.
In most cases functional tests will not need to be written, however you should confirm this with a maintainer. In most cases functional tests will not need to be written, however you should confirm this with a maintainer.
Integration tests **will** be required if the development introduces code that interacts with an independent third-party application. Integration tests **will** be required if the development introduces code that interacts with an independent third-party application.
## Available Test classes
To aid in development we have written test classes that you can inherit from for your test classes
- API Permission Checks
_These tests ensure that only a user with the correct permissions can perform an action against a Model within Centurion_
- `api.tests.abstract.api_permissions_viewset.APIPermissionAdd` _Add permission checks_
- `api.tests.abstract.api_permissions_viewset.APIPermissionChange` _Change permission check_
- `api.tests.abstract.api_permissions_viewset.APIPermissionDelete` _Delete permission check_
- `api.tests.abstract.api_permissions_viewset.APIPermissionView` _View permission check_
- `api.tests.abstract.api_permissions_viewset.APIPermissions` _Add, Change, Delete and View permission checks_
## Writing Tests ## Writing Tests
We use class based tests. Each class will require a `setUpTestData` method for test setup. To furhter assist in the writing of tests, we have written the test cases for common items as an abstract class. You are advised to review the [test cases](./api/tests/index.md) and if it's applicable to the item you have added, than add the test case class to be inherited by your test class. We use class based tests. Each class will require a `setUpTestData` method for test setup. To furhter assist in the writing of tests, we have written the test cases for common items as an abstract class. You are advised to inherit from our test classes _(see above)_ as a strating point and extend from there.
Naming of test classes is in `CamelCase` in format `<Model Name><what's being tested>` for example the class name for device model history entry tests would be `DeviceHistory`. Naming of test classes is in `CamelCase` in format `<Model Name><what's being tested>` for example the class name for device model history entry tests would be `DeviceHistory`.
@ -30,7 +52,6 @@ Example of a model history test class.
``` py ``` py
import pytest import pytest
import unittest
import requests import requests
from django.test import TestCase, Client from django.test import TestCase, Client
@ -82,22 +103,19 @@ example file system structure showing the layout of the tests directory for a mo
│      ├── test_<model name>_core_history.py │      ├── test_<model name>_core_history.py
│      ├── test_<model name>_history_permission.py │      ├── test_<model name>_history_permission.py
│      ├── test_<model name>.py │      ├── test_<model name>.py
│      └── test_<model name>_views.py │      └── test_<model name>_viewsets.py
``` ```
Tests are broken up into the type the test is (sub-directory to test), and they are `unit`, `functional`, `UI` and `integration`. These sub-directories each contain a sub-directory for each model they are testing. Tests are broken up into the type the test is (sub-directory to test), and they are `unit`, `functional`, `UI` and `integration`. These sub-directories each contain a sub-directory for each model they are testing.
Items to test include, and are not limited to: Items to test include, and are not limited to:
- CRUD permissions admin site - CRUD permissions admin site
- CRUD permissions api site - [ModelPermissions (API)](./api/tests/model_permissions_api.md) - CRUD permissions api site
- CRUD permissions main site - [ModelPermissions](./api/tests/model_permissions.md) - can only access organization object
- can only access organization object - [ModelPermissions](./api/tests/model_permissions.md), [ModelPermissions (API)](./api/tests/model_permissions_api.md)
- can access global object (still to require model CRUD permission) - can access global object (still to require model CRUD permission)

View File

@ -139,16 +139,6 @@ nav:
- projects/centurion_erp/development/api/tests/model_permission_view_organization_manager.md - projects/centurion_erp/development/api/tests/model_permission_view_organization_manager.md
- projects/centurion_erp/development/api/tests/model_permissions_api.md
- projects/centurion_erp/development/api/tests/model_permission_api_add.md
- projects/centurion_erp/development/api/tests/model_permission_api_change.md
- projects/centurion_erp/development/api/tests/model_permission_api_delete.md
- projects/centurion_erp/development/api/tests/model_permission_api_view.md
- projects/centurion_erp/development/api/tests/model_tenancy_object.md - projects/centurion_erp/development/api/tests/model_tenancy_object.md
- projects/centurion_erp/development/api/tests/model_views.md - projects/centurion_erp/development/api/tests/model_views.md