Skip to content

Commit

Permalink
Run backend unit tests in Github Actions
Browse files Browse the repository at this point in the history
  • Loading branch information
Boomaa23 committed Aug 31, 2024
1 parent a5e3990 commit cfe18e9
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 57 deletions.
19 changes: 19 additions & 0 deletions .github/workflows/unittest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: unittest

on:
pull_request:
push:
branches: [main]

jobs:
unittest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v3
- run: |
cd server
python -m pip install -r requirements.txt
- run: |
cd server
python -m unittest discover -v
Empty file added server/src/__init__.py
Empty file.
4 changes: 3 additions & 1 deletion server/src/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def get(id_: str, entity_type: Type[models.Model]):
res = cursor.execute(f'SELECT * FROM {entity_type.table_name} WHERE {entity_type.id_name}=?', (id_,))
db_entity = res.fetchone()
if db_entity is None or len(db_entity) == 0:
flask.abort(404, 'Item does not exist')
flask.abort(404, f'{entity_type.__name__} does not exist')
entity = entity_type(*db_entity)
return entity.to_response()

Expand All @@ -37,6 +37,8 @@ def update(entity_type: Type[models.Model], immutable_props: list[str]):
# TODO have a better solution for mutability
entity_properties = models.get_entity_parameters(entity_type)
for immutable_prop in immutable_props:
if immutable_prop in form.form:
flask.abort(400, f'Immutable property {immutable_prop} found in request body')
entity_properties.pop(immutable_prop)

properties_to_update = {k: v for k, v in entity_properties.items() if k in form.form}
Expand Down
11 changes: 11 additions & 0 deletions server/tst/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import os
import sys


_project_path = os.getcwd()

_source_path = os.path.join(_project_path, 'src')
sys.path.append(_source_path)

_test_path = os.path.join(_project_path, 'tst')
sys.path.append(_test_path)
56 changes: 0 additions & 56 deletions server/tst/api_box_routes_test.py

This file was deleted.

196 changes: 196 additions & 0 deletions server/tst/test_api_box_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import json
import unittest
from typing import Optional

import auth
import models
import tstutil
from identifier import Identifier


class TestBoxCreate(tstutil.TestBase):
scope = auth.Scope.BOX_CREATE

def test_200(self):
attrs = {
'name': 'tst-box-name',
'api_key': self.superuser.api_key,
}
resp_json = self.call_route_assert_code(200, attrs)
entity_json = self.assert_single_entity(
resp_json, {
'name': attrs['name'],
},
)
self.assertIsNotNone(Identifier(length=models.Box.id_length, id_=entity_json[models.Box.id_name]))

def test_400_no_name(self):
attrs = {
'api_key': self.superuser.api_key,
}
self.call_route_assert_code(400, attrs)

def test_400_malformed_name(self):
attrs = {
'name': '*',
'api_key': self.superuser.api_key,
}
self.call_route_assert_code(400, attrs)

def test_400_duplicate_name(self):
attrs = {
'name': 'tst-box-duplicate-name',
'api_key': self.superuser.api_key,
}
self.call_route_assert_code(200, attrs)
self.call_route_assert_code(400, attrs)

def call_route(self, attrs: Optional[dict[str, str]] = None):
return self.client.post('/api/box/create', data=attrs)


class TestBoxGet(tstutil.TestBase):
scope = auth.Scope.BOX_GET

def setUp(self):
super().setUp()
attrs = {
'name': 'tst-box-get',
'api_key': self.superuser.api_key,
}
response = self.client.post('/api/box/create', data=attrs)
self.box = models.Box(*json.loads(response.data)['body'][0].values())

def test_200(self):
attrs = {
models.Box.id_name: self.box.box_id,
}
resp_json = self.call_route_assert_code(200, attrs)
self.assert_single_entity(
resp_json, {
models.Box.id_name: self.box.box_id,
'name': self.box.name,
},
)

def test_400_malformed_id(self):
attrs = {
models.Box.id_name: '*',
}
self.call_route_assert_code(400, attrs)

def test_400_no_id(self):
attrs = {}
self.call_route_assert_code(400, attrs)

def test_404_nonexistent_box(self):
attrs = {
models.Box.id_name: 'tst-box-get-nonexistent-id',
}
self.call_route_assert_code(404, attrs)

def test_400_no_apikey(self):
# No authentication required
pass

def test_400_malformed_apikey(self):
# No authentication required
pass

def test_401_user_not_found(self):
# No authentication required
pass

def test_403_user_unauthorized(self):
# No authentication required
pass

def call_route(self, attrs: dict[str, str]):
return self.client.post('/api/box/get', data=attrs)


class TestBoxUpdate(tstutil.TestBase):
scope = auth.Scope.BOX_UPDATE

def test_200(self):
pass

def test_200_without_verification(self):
pass

def test_400_malformed_id(self):
pass

def test_400_no_id(self):
pass

def test_404_nonexistent_box(self):
pass

def test_400_change_immutable_properties(self):
pass

def test_400_no_update_properties(self):
pass

def test_400_malformed_update_properties(self):
pass

def call_route(self, attrs: dict[str, str]):
return self.client.post('/api/box/update', data=attrs)


class TestBoxRemove(tstutil.TestBase):
scope = auth.Scope.BOX_REMOVE

def test_200(self):
pass

def test_200_without_verification(self):
pass

def test_400_malformed_id(self):
pass

def test_400_no_id(self):
pass

def test_404_nonexistent_box(self):
pass

def test_500_duplicate_id(self):
pass

def call_route(self, attrs: dict[str, str]):
return self.client.post('/api/box/remove', data=attrs)


# TODO finish
class TestBoxesList(tstutil.TestBase):
scope = auth.Scope.BOXES_LIST

def test_200(self):
pass

def test_400_no_apikey(self):
# No authentication required
pass

def test_400_malformed_apikey(self):
# No authentication required
pass

def test_401_user_not_found(self):
# No authentication required
pass

def test_403_user_unauthorized(self):
# No authentication required
pass

def call_route(self, attrs: dict[str, str]):
return self.client.post('/api/boxes/list', data=attrs)


if __name__ == '__main__':
unittest.main()
14 changes: 14 additions & 0 deletions server/tst/testutil.py → server/tst/tstutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ def call_route_assert_code(self, status_code: int, attrs: Optional[dict[str, Any
self.assertEqual(status_code, resp_json['code'])
return resp_json

def assert_single_entity(self, resp_json, expected: dict[str, str]):
self.assertIn('body', resp_json)
self.assertIsNotNone(resp_json['body'])
self.assertEqual(1, len(resp_json['body']))
entity_json = resp_json['body'][0]
self.assertIsNotNone(entity_json)
for key in expected.keys():
self.assertIn(key, entity_json)
self.assertEqual(expected[key], entity_json[key])
return entity_json

def test_400_no_apikey(self):
attrs = {}
self.call_route_assert_code(400, attrs)
Expand All @@ -61,6 +72,9 @@ def test_403_user_unauthorized(self):
}
self.call_route_assert_code(403, attrs)

def test_200(self):
raise NotImplementedError()

def call_route(self, attrs: dict[str, str]):
raise NotImplementedError()

Expand Down

0 comments on commit cfe18e9

Please sign in to comment.