Merge pull request #947 from nofusscomputing/initial-integration-tests
This commit is contained in:
13
.github/workflows/ci.yaml
vendored
13
.github/workflows/ci.yaml
vendored
@ -46,6 +46,19 @@ jobs:
|
||||
WORKFLOW_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
|
||||
|
||||
|
||||
integration-test:
|
||||
name: 'Integration Test'
|
||||
uses: nofusscomputing/action_python/.github/workflows/python-integration.yaml@development
|
||||
needs:
|
||||
- docker
|
||||
with:
|
||||
POSTGRES_VERSIONS: '[ "13", "14", "15", "16", "17" ]'
|
||||
PYTHON_VERSION: '3.11'
|
||||
RABBITMQ_VERSIONS: '[ "3.12", "3.13", "4.0", "4.1" ]'
|
||||
secrets:
|
||||
WORKFLOW_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
|
||||
|
||||
|
||||
gitlab-mirror:
|
||||
if: ${{ github.repository == 'nofusscomputing/centurion_erp' }}
|
||||
runs-on: ubuntu-latest
|
||||
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -21,3 +21,8 @@ feature_flags.json
|
||||
coverage_*.json
|
||||
*-coverage.xml
|
||||
log/
|
||||
# Integration testing
|
||||
app/artifacts/
|
||||
app/pyproject.toml
|
||||
app/histogram_**
|
||||
app/counter_**
|
||||
|
@ -1,9 +1,10 @@
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import (
|
||||
ContentType,
|
||||
Permission
|
||||
)
|
||||
from django.conf import settings
|
||||
from django.db.models import QuerySet
|
||||
|
||||
|
||||
def permission_queryset():
|
||||
@ -61,47 +62,66 @@ def permission_queryset():
|
||||
|
||||
if not settings.RUNNING_TESTS:
|
||||
|
||||
models = apps.get_models()
|
||||
try:
|
||||
# This blocks purpose is to cater for fresh install
|
||||
# so that the app does not crash before the DB is setup.
|
||||
|
||||
for model in models:
|
||||
models = apps.get_models()
|
||||
|
||||
if(
|
||||
not str(model._meta.object_name).endswith('AuditHistory')
|
||||
and not str(model._meta.model_name).lower().endswith('history')
|
||||
):
|
||||
# check `endswith('history')` can be removed when the old history models are removed
|
||||
continue
|
||||
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = model._meta.app_label,
|
||||
model = model._meta.model_name
|
||||
)
|
||||
|
||||
permissions = Permission.objects.filter(
|
||||
content_type = content_type,
|
||||
)
|
||||
|
||||
for permission in permissions:
|
||||
for model in models:
|
||||
|
||||
if(
|
||||
not permission.codename == 'view_' + str(model._meta.model_name)
|
||||
and str(model._meta.object_name).endswith('AuditHistory')
|
||||
):
|
||||
exclude_permissions += [ permission.codename ]
|
||||
|
||||
elif(
|
||||
not str(model._meta.object_name).endswith('AuditHistory')
|
||||
and str(model._meta.model_name).lower().endswith('history')
|
||||
and not str(model._meta.model_name).lower().endswith('history')
|
||||
):
|
||||
# This `elif` can be removed when the old history models are removed
|
||||
# check `endswith('history')` can be removed when the old history models are removed
|
||||
continue
|
||||
|
||||
exclude_permissions += [ permission.codename ]
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = model._meta.app_label,
|
||||
model = model._meta.model_name
|
||||
)
|
||||
|
||||
permissions = Permission.objects.filter(
|
||||
content_type = content_type,
|
||||
)
|
||||
|
||||
for permission in permissions:
|
||||
|
||||
if(
|
||||
not permission.codename == 'view_' + str(model._meta.model_name)
|
||||
and str(model._meta.object_name).endswith('AuditHistory')
|
||||
):
|
||||
exclude_permissions += [ permission.codename ]
|
||||
|
||||
elif(
|
||||
not str(model._meta.object_name).endswith('AuditHistory')
|
||||
and str(model._meta.model_name).lower().endswith('history')
|
||||
):
|
||||
# This `elif` can be removed when the old history models are removed
|
||||
|
||||
exclude_permissions += [ permission.codename ]
|
||||
|
||||
|
||||
return Permission.objects.select_related('content_type').filter(
|
||||
content_type__app_label__in = centurion_apps,
|
||||
).exclude(
|
||||
content_type__model__in = exclude_models
|
||||
).exclude(
|
||||
codename__in = exclude_permissions
|
||||
)
|
||||
return Permission.objects.select_related('content_type').filter(
|
||||
content_type__app_label__in = centurion_apps,
|
||||
).exclude(
|
||||
content_type__model__in = exclude_models
|
||||
).exclude(
|
||||
codename__in = exclude_permissions
|
||||
)
|
||||
|
||||
except:
|
||||
pass
|
||||
|
||||
return QuerySet()
|
||||
|
||||
else:
|
||||
|
||||
return Permission.objects.select_related('content_type').filter(
|
||||
content_type__app_label__in = centurion_apps,
|
||||
).exclude(
|
||||
content_type__model__in = exclude_models
|
||||
).exclude(
|
||||
codename__in = exclude_permissions
|
||||
)
|
||||
|
@ -34,17 +34,17 @@ router = DefaultRouter(trailing_slash=False)
|
||||
router.register('', access_v2.Index, basename = '_api_v2_access_home')
|
||||
|
||||
router.register(
|
||||
prefix = '(?P<model_name>[company]+)', viewset = entity.ViewSet,
|
||||
prefix = '/(?P<model_name>[company]+)', viewset = entity.ViewSet,
|
||||
feature_flag = '2025-00008',basename = '_api_v2_company'
|
||||
)
|
||||
|
||||
router.register(
|
||||
prefix=f'entity/(?P<model_name>[{entity_type_names}]+)?', viewset = entity.ViewSet,
|
||||
prefix=f'/entity/(?P<model_name>[{entity_type_names}]+)?', viewset = entity.ViewSet,
|
||||
feature_flag = '2025-00002', basename = '_api_entity_sub'
|
||||
)
|
||||
|
||||
router.register(
|
||||
prefix = 'entity', viewset = entity.NoDocsViewSet,
|
||||
prefix = '/entity', viewset = entity.NoDocsViewSet,
|
||||
feature_flag = '2025-00002', basename = '_api_entity'
|
||||
)
|
||||
|
||||
@ -54,7 +54,7 @@ router.register(
|
||||
# )
|
||||
|
||||
router.register(
|
||||
prefix = 'tenant', viewset = organization.ViewSet,
|
||||
prefix = '/tenant', viewset = organization.ViewSet,
|
||||
basename = '_api_tenant'
|
||||
)
|
||||
|
||||
@ -64,7 +64,7 @@ router.register(
|
||||
# )
|
||||
|
||||
router.register(
|
||||
prefix = 'tenant/(?P<organization_id>[0-9]+)/team', viewset = team_v2.ViewSet,
|
||||
prefix = '/tenant/(?P<organization_id>[0-9]+)/team', viewset = team_v2.ViewSet,
|
||||
basename = '_api_v2_organization_team'
|
||||
)
|
||||
|
||||
@ -75,13 +75,13 @@ router.register(
|
||||
# )
|
||||
|
||||
router.register(
|
||||
prefix = 'access/tenant/(?P<organization_id>[0-9]+)/team/(?P<team_id>[0-9]+)/user',
|
||||
prefix = '/access/tenant/(?P<organization_id>[0-9]+)/team/(?P<team_id>[0-9]+)/user',
|
||||
viewset = team_user_v2.ViewSet,
|
||||
basename = '_api_v2_organization_team_user'
|
||||
)
|
||||
|
||||
router.register(
|
||||
prefix = 'role', viewset = role.ViewSet,
|
||||
prefix = '/role', viewset = role.ViewSet,
|
||||
feature_flag = '2025-00003', basename = '_api_role'
|
||||
)
|
||||
|
||||
|
@ -42,7 +42,7 @@ asset_type_names = str(asset_type_names)[:-1]
|
||||
if not asset_type_names:
|
||||
asset_type_names = 'none'
|
||||
|
||||
router.register(f'asset/(?P<model_name>[{asset_type_names}]+)?', asset.ViewSet, feature_flag = '2025-00004', basename='_api_asset_sub')
|
||||
router.register('asset', asset.NoDocsViewSet, feature_flag = '2025-00004', basename='_api_asset')
|
||||
router.register(f'/asset/(?P<model_name>[{asset_type_names}]+)?', asset.ViewSet, feature_flag = '2025-00004', basename='_api_asset_sub')
|
||||
router.register('/asset', asset.NoDocsViewSet, feature_flag = '2025-00004', basename='_api_asset')
|
||||
|
||||
urlpatterns = router.urls
|
||||
|
@ -61,22 +61,22 @@ router = DefaultRouter(trailing_slash=False)
|
||||
router.register('', v2.Index, basename='_api_v2_home')
|
||||
|
||||
|
||||
router.register('base', base_index_v2.Index, basename='_api_v2_base_home')
|
||||
router.register('base/content_type', content_type_v2.ViewSet, basename='_api_v2_content_type')
|
||||
router.register('base/permission', permission_v2.ViewSet, basename='_api_v2_permission')
|
||||
router.register('base/user', user_v2.ViewSet, basename='_api_v2_user')
|
||||
router.register('/base', base_index_v2.Index, basename='_api_v2_base_home')
|
||||
router.register('/base/content_type', content_type_v2.ViewSet, basename='_api_v2_content_type')
|
||||
router.register('/base/permission', permission_v2.ViewSet, basename='_api_v2_permission')
|
||||
router.register('/base/user', user_v2.ViewSet, basename='_api_v2_user')
|
||||
|
||||
|
||||
|
||||
router.register(
|
||||
prefix = f'(?P<app_label>[{history_app_labels}]+)/(?P<model_name>[{history_type_names} \
|
||||
prefix = f'/(?P<app_label>[{history_app_labels}]+)/(?P<model_name>[{history_type_names} \
|
||||
]+)/(?P<model_id>[0-9]+)/history',
|
||||
viewset = audit_history.ViewSet,
|
||||
basename = '_api_centurionaudit_sub'
|
||||
)
|
||||
|
||||
router.register(
|
||||
prefix = f'(?P<app_label>[{notes_app_labels}]+)/(?P<model_name>[{notes_type_names} \
|
||||
prefix = f'/(?P<app_label>[{notes_app_labels}]+)/(?P<model_name>[{notes_type_names} \
|
||||
]+)/(?P<model_id>[0-9]+)/notes',
|
||||
viewset = centurion_model_notes.ViewSet,
|
||||
basename = '_api_centurionmodelnote_sub'
|
||||
@ -85,24 +85,24 @@ router.register(
|
||||
|
||||
urlpatterns = [
|
||||
|
||||
path('schema', SpectacularAPIView.as_view(api_version='v2'), name='schema-v2',),
|
||||
path('docs', SpectacularSwaggerView.as_view(url_name='schema-v2'), name='_api_v2_docs'),
|
||||
path('/schema', SpectacularAPIView.as_view(api_version='v2'), name='schema-v2',),
|
||||
path('/docs', SpectacularSwaggerView.as_view(url_name='schema-v2'), name='_api_v2_docs'),
|
||||
|
||||
]
|
||||
|
||||
urlpatterns += router.urls
|
||||
|
||||
urlpatterns += [
|
||||
path(route = "access/", view = include("access.urls_api")),
|
||||
path(route = "accounting/", view = include("accounting.urls")),
|
||||
path(route = "assistance/", view = include("assistance.urls_api")),
|
||||
path(route = "config_management/", view = include("config_management.urls_api")),
|
||||
path(route = "core/", view = include("core.urls_api")),
|
||||
path(route = "devops/", view = include("devops.urls")),
|
||||
path(route = "hr/", view = include('human_resources.urls')),
|
||||
path(route = "itam/", view = include("itam.urls_api")),
|
||||
path(route = "itim/", view = include("itim.urls_api")),
|
||||
path(route = "project_management/", view = include("project_management.urls_api")),
|
||||
path(route = "settings/", view = include("settings.urls_api")),
|
||||
path(route = 'public/', view = include('api.urls_public')),
|
||||
path(route = "/access", view = include("access.urls_api")),
|
||||
path(route = "/accounting", view = include("accounting.urls")),
|
||||
path(route = "/assistance", view = include("assistance.urls_api")),
|
||||
path(route = "/config_management", view = include("config_management.urls_api")),
|
||||
path(route = "/core", view = include("core.urls_api")),
|
||||
path(route = "/devops", view = include("devops.urls")),
|
||||
path(route = "/hr", view = include('human_resources.urls')),
|
||||
path(route = "/itam", view = include("itam.urls_api")),
|
||||
path(route = "/itim", view = include("itim.urls_api")),
|
||||
path(route = "/project_management", view = include("project_management.urls_api")),
|
||||
path(route = "/settings", view = include("settings.urls_api")),
|
||||
path(route = '/public', view = include('api.urls_public')),
|
||||
]
|
||||
|
@ -19,16 +19,16 @@ router.register(
|
||||
basename = '_api_v2_assistance_home'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'knowledge_base', viewset = knowledge_base_v2.ViewSet,
|
||||
prefix = '/knowledge_base', viewset = knowledge_base_v2.ViewSet,
|
||||
basename = '_api_knowledgebase'
|
||||
)
|
||||
router.register(
|
||||
prefix = '(?P<model>.+)/(?P<model_pk>[0-9]+)/knowledge_base',
|
||||
prefix = '/(?P<model>.+)/(?P<model_pk>[0-9]+)/knowledge_base',
|
||||
viewset = model_knowledge_base_article.ViewSet,
|
||||
basename = '_api_v2_model_kb'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'ticket/request', viewset = request_ticket_v2.ViewSet,
|
||||
prefix = '/ticket/request', viewset = request_ticket_v2.ViewSet,
|
||||
basename = '_api_v2_ticket_request'
|
||||
)
|
||||
|
||||
|
@ -20,6 +20,7 @@ import django.db.models.options as options
|
||||
|
||||
options.DEFAULT_NAMES = (*options.DEFAULT_NAMES, 'sub_model_type', 'itam_sub_model_type')
|
||||
|
||||
APPEND_SLASH = False
|
||||
AUTH_USER_MODEL = 'auth.User'
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
|
182
app/centurion/tests/integration/test_integration_list_urls.py
Normal file
182
app/centurion/tests/integration/test_integration_list_urls.py
Normal file
@ -0,0 +1,182 @@
|
||||
|
||||
import pytest
|
||||
import re
|
||||
import requests
|
||||
|
||||
from django.urls import get_resolver, URLPattern, URLResolver
|
||||
|
||||
|
||||
|
||||
def list_urls(urlpatterns, parent_pattern=''):
|
||||
|
||||
urls = []
|
||||
|
||||
for entry in urlpatterns:
|
||||
|
||||
if isinstance(entry, URLPattern):
|
||||
urls.append(parent_pattern + str(entry.pattern))
|
||||
|
||||
elif isinstance(entry, URLResolver):
|
||||
urls.extend(list_urls(entry.url_patterns, parent_pattern + str(entry.pattern)))
|
||||
|
||||
filtered = [
|
||||
re.sub(r"\^([a-z\-]+)\$$", r"\1", u).rstrip('/') for u in urls if (
|
||||
re.sub(r"\^([a-z\-]+)\$$", r"\1", u).startswith('api/')
|
||||
and '(' not in re.sub(r"\^([a-z\-]+)\$$", r"\1", u).rstrip('/')
|
||||
and '<' not in re.sub(r"\^([a-z\-]+)\$$", r"\1", u).rstrip('/')
|
||||
and '$' not in re.sub(r"\^([a-z\-]+)\$$", r"\1", u).rstrip('/')
|
||||
)
|
||||
]
|
||||
|
||||
return filtered
|
||||
|
||||
|
||||
no_auth_urls = [
|
||||
'api/v2/auth/login',
|
||||
'api/v2/docs',
|
||||
'api/v2/schema',
|
||||
]
|
||||
|
||||
urls_list_view_auth_required_excluded = [
|
||||
'api/v2/auth/logout',
|
||||
|
||||
]
|
||||
|
||||
urls_list_view_auth_required_authenticated_excluded = [
|
||||
'api/v2/itam/inventory',
|
||||
'api/v2/auth/logout',
|
||||
]
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.regression
|
||||
class URLChecksPyTest:
|
||||
|
||||
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def auto_login_client(self):
|
||||
session = requests.Session()
|
||||
|
||||
login_page_url = "http://127.0.0.1:8003/api/v2/auth/login"
|
||||
login_post_url = "http://127.0.0.1:8003/api/v2/auth/login"
|
||||
|
||||
resp = session.get(login_page_url)
|
||||
resp.raise_for_status()
|
||||
# Extract CSRF token from cookies (Django sets csrftoken cookie)
|
||||
csrf_token = session.cookies.get("csrftoken")
|
||||
if not csrf_token:
|
||||
raise RuntimeError("CSRF token cookie not found")
|
||||
|
||||
login_data = {
|
||||
"username": "admin",
|
||||
"password": "admin",
|
||||
"csrfmiddlewaretoken": csrf_token,
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Referer": login_page_url,
|
||||
"X-CSRFToken": csrf_token, # Include CSRF token header
|
||||
}
|
||||
|
||||
resp = session.post(login_post_url, data=login_data, headers=headers, allow_redirects=True)
|
||||
resp.raise_for_status()
|
||||
|
||||
|
||||
class Client:
|
||||
def __init__(self, session):
|
||||
self._session = session
|
||||
|
||||
self._unauth_session = requests.Session()
|
||||
|
||||
resp = self._unauth_session.get(login_page_url)
|
||||
resp.raise_for_status()
|
||||
self._headers = csrf_token = {
|
||||
"Referer": login_page_url,
|
||||
"X-CSRFToken": self._unauth_session.cookies.get("csrftoken"),
|
||||
}
|
||||
|
||||
def request(self, method, url, auth = False, **kwargs):
|
||||
|
||||
if auth:
|
||||
session = self._session
|
||||
else:
|
||||
session = self._unauth_session
|
||||
|
||||
return session.request(method, url, headers=self._headers, **kwargs)
|
||||
|
||||
@property
|
||||
def cookies(self):
|
||||
return self._session.cookies
|
||||
|
||||
return Client(session)
|
||||
|
||||
|
||||
list_view_urls = list_urls(urlpatterns = get_resolver().url_patterns)
|
||||
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
argnames = "url_path",
|
||||
argvalues = [
|
||||
url for url in list_view_urls if( url in no_auth_urls )
|
||||
],
|
||||
ids = [
|
||||
re.sub(r'[^\w_\-.:]', '_', url) for url in list_view_urls if( url in no_auth_urls )
|
||||
],
|
||||
)
|
||||
def test_urls_no_auth_required(self, url_path, auto_login_client):
|
||||
url = f"http://127.0.0.1:8003/{url_path}"
|
||||
|
||||
response = auto_login_client.request("GET", url)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
|
||||
@pytest.mark.permissions
|
||||
@pytest.mark.parametrize(
|
||||
argnames = "url_path",
|
||||
argvalues = [
|
||||
url for url in list_view_urls if(
|
||||
url not in no_auth_urls
|
||||
and url not in urls_list_view_auth_required_excluded
|
||||
)
|
||||
],
|
||||
ids = [
|
||||
re.sub(r'[^\w_\-.:]', '_', url) for url in list_view_urls if(
|
||||
url not in no_auth_urls
|
||||
and url not in urls_list_view_auth_required_excluded
|
||||
)
|
||||
],
|
||||
)
|
||||
def test_urls_list_view_auth_required(self, url_path, auto_login_client):
|
||||
url = f"http://127.0.0.1:8003/{url_path}"
|
||||
|
||||
response = auto_login_client.request("GET", url)
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
|
||||
@pytest.mark.permissions
|
||||
@pytest.mark.parametrize(
|
||||
argnames = "url_path",
|
||||
argvalues = [
|
||||
url for url in list_view_urls if(
|
||||
url not in no_auth_urls
|
||||
and url not in urls_list_view_auth_required_authenticated_excluded
|
||||
)
|
||||
],
|
||||
ids = [
|
||||
re.sub(r'[^\w_\-.:]', '_', url) for url in list_view_urls if(
|
||||
url not in no_auth_urls
|
||||
and url not in urls_list_view_auth_required_authenticated_excluded
|
||||
)
|
||||
],
|
||||
)
|
||||
def test_urls_list_view_auth_required_authenticated(self, url_path, auto_login_client):
|
||||
url = f"http://127.0.0.1:8003/{url_path}"
|
||||
|
||||
response = auto_login_client.request(method = "GET", url = url, auth = True)
|
||||
|
||||
assert response.status_code == 200
|
@ -4,14 +4,14 @@ from django.contrib.auth import views as auth_views
|
||||
from django.views.static import serve
|
||||
from django.urls import include, path, re_path
|
||||
|
||||
|
||||
from rest_framework import urls
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls, name='_administration'),
|
||||
path('admin', admin.site.urls, name='_administration'),
|
||||
|
||||
path('account/password_change/', auth_views.PasswordChangeView.as_view(template_name="password_change.html.j2"), name="change_password"),
|
||||
path('account/password_change', auth_views.PasswordChangeView.as_view(template_name="password_change.html.j2"), name="change_password"),
|
||||
|
||||
path("account/", include("django.contrib.auth.urls")),
|
||||
path("account", include("django.contrib.auth.urls")),
|
||||
|
||||
re_path(r'^static/(?P<path>.*)$', serve,{'document_root': settings.STATIC_ROOT}),
|
||||
|
||||
@ -30,15 +30,16 @@ if settings.API_ENABLED:
|
||||
|
||||
urlpatterns += [
|
||||
|
||||
path("api/", include("api.urls", namespace = 'v1')),
|
||||
path("api", include("api.urls", namespace = 'v1')),
|
||||
|
||||
path("api/v2/", include("api.urls_v2", namespace = 'v2')),
|
||||
path("api/v2", include("api.urls_v2", namespace = 'v2')),
|
||||
|
||||
]
|
||||
|
||||
|
||||
urlpatterns += [
|
||||
path('api/v2/auth/', include('rest_framework.urls')),
|
||||
path('api/v2/auth/login', auth_views.LoginView.as_view(template_name='rest_framework/login.html'), name='login'),
|
||||
path('api/v2/auth/logout', auth_views.LogoutView.as_view(), name='logout'),
|
||||
]
|
||||
|
||||
|
||||
|
@ -18,15 +18,15 @@ router.register(
|
||||
basename = '_api_v2_config_management_home'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'group', viewset = config_group_v2.ViewSet,
|
||||
prefix = '/group', viewset = config_group_v2.ViewSet,
|
||||
basename = '_api_configgroups'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'group/(?P<parent_group>[0-9]+)/child_group', viewset = config_group_v2.ViewSet,
|
||||
prefix = '/group/(?P<parent_group>[0-9]+)/child_group', viewset = config_group_v2.ViewSet,
|
||||
basename = '_api_configgroups_child'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'group/(?P<config_group_id>[0-9]+)/software',
|
||||
prefix = '/group/(?P<config_group_id>[0-9]+)/software',
|
||||
viewset = config_group_software_v2.ViewSet,
|
||||
basename = '_api_configgroupsoftware'
|
||||
)
|
||||
|
@ -41,59 +41,59 @@ router: DefaultRouter = DefaultRouter(trailing_slash=False)
|
||||
|
||||
|
||||
router.register(
|
||||
'history', audit_history.NoDocsViewSet,
|
||||
'/history', audit_history.NoDocsViewSet,
|
||||
basename = '_api_centurionaudit'
|
||||
)
|
||||
|
||||
|
||||
|
||||
router.register(
|
||||
prefix=f'ticket', viewset = ticket.NoDocsViewSet,
|
||||
prefix=f'/ticket', viewset = ticket.NoDocsViewSet,
|
||||
feature_flag = '2025-00006', basename = '_api_ticketbase'
|
||||
)
|
||||
router.register(
|
||||
prefix=f'ticket/(?P<ticket_type>[{ticket_type_names}]+)', viewset = ticket.ViewSet,
|
||||
prefix=f'/ticket/(?P<ticket_type>[{ticket_type_names}]+)', viewset = ticket.ViewSet,
|
||||
feature_flag = '2025-00006', basename = '_api_ticketbase_sub'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'ticket/(?P<ticket_id>[0-9]+)/comment', viewset = ticket_comment.NoDocsViewSet,
|
||||
prefix = '/ticket/(?P<ticket_id>[0-9]+)/comment', viewset = ticket_comment.NoDocsViewSet,
|
||||
feature_flag = '2025-00006', basename = '_api_ticket_comment_base'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'ticket/(?P<ticket_id>[0-9]+)/comment/(?P<parent_id>[0-9]+)/threads',
|
||||
prefix = '/ticket/(?P<ticket_id>[0-9]+)/comment/(?P<parent_id>[0-9]+)/threads',
|
||||
viewset = ticket_comment.ViewSet,
|
||||
feature_flag = '2025-00006', basename = '_api_ticket_comment_base_thread'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'ticket/(?P<ticket_id>[0-9]+)/comments', viewset = ticket_comment_depreciated.ViewSet,
|
||||
prefix = '/ticket/(?P<ticket_id>[0-9]+)/comments', viewset = ticket_comment_depreciated.ViewSet,
|
||||
basename = '_api_v2_ticket_comment'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'ticket/(?P<ticket_id>[0-9]+)/comments/(?P<parent_id>[0-9]+)/threads',
|
||||
prefix = '/ticket/(?P<ticket_id>[0-9]+)/comments/(?P<parent_id>[0-9]+)/threads',
|
||||
viewset = ticket_comment_depreciated.ViewSet,
|
||||
basename = '_api_v2_ticket_comment_threads'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'ticket/(?P<ticket_id>[0-9]+)/linked_item', viewset = ticket_linked_item.ViewSet,
|
||||
prefix = '/ticket/(?P<ticket_id>[0-9]+)/linked_item', viewset = ticket_linked_item.ViewSet,
|
||||
basename = '_api_v2_ticket_linked_item'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'ticket/(?P<ticket_id>[0-9]+)/related_ticket', viewset = related_ticket.ViewSet,
|
||||
prefix = '/ticket/(?P<ticket_id>[0-9]+)/related_ticket', viewset = related_ticket.ViewSet,
|
||||
basename = '_api_v2_ticket_related'
|
||||
)
|
||||
router.register(
|
||||
prefix=f'ticket/(?P<ticket_id>[0-9]+)/(?P<ticket_comment_model>[{ticket_comment_names}]+)',
|
||||
prefix=f'/ticket/(?P<ticket_id>[0-9]+)/(?P<ticket_comment_model>[{ticket_comment_names}]+)',
|
||||
viewset = ticket_comment.ViewSet,
|
||||
feature_flag = '2025-00006', basename = '_api_ticket_comment_base_sub'
|
||||
)
|
||||
router.register(
|
||||
prefix=f'ticket/(?P<ticket_id>[0-9]+)/(?P<ticket_comment_model>[{ticket_comment_names} \
|
||||
prefix=f'/ticket/(?P<ticket_id>[0-9]+)/(?P<ticket_comment_model>[{ticket_comment_names} \
|
||||
]+)/(?P<parent_id>[0-9]+)/threads',
|
||||
viewset = ticket_comment.ViewSet,
|
||||
feature_flag = '2025-00006', basename = '_api_ticket_comment_base_sub_thread'
|
||||
)
|
||||
router.register(
|
||||
prefix = '(?P<item_class>[a-z_]+)/(?P<item_id>[0-9]+)/item_ticket',
|
||||
prefix = '/(?P<item_class>[a-z_]+)/(?P<item_id>[0-9]+)/item_ticket',
|
||||
viewset = ticket_linked_item.ViewSet,
|
||||
basename = '_api_v2_item_tickets'
|
||||
)
|
||||
|
@ -13,21 +13,21 @@ app_name = "devops"
|
||||
router = DefaultRouter(trailing_slash=False)
|
||||
|
||||
router.register(
|
||||
prefix = 'feature_flag', viewset = feature_flag.ViewSet,
|
||||
prefix = '/feature_flag', viewset = feature_flag.ViewSet,
|
||||
basename = '_api_featureflag'
|
||||
)
|
||||
router.register(
|
||||
prefix = r'git_repository(?:/(?P<model_name>gitlab|github))?',
|
||||
prefix = r'/git_repository(?:/(?P<model_name>gitlab|github))?',
|
||||
viewset = git_repository.ViewSet,
|
||||
feature_flag = '2025-00001', basename = '_api_gitrepository'
|
||||
)
|
||||
router.register(
|
||||
prefix = r'(?P<model_name>githubrepository|gitlabrepository)',
|
||||
prefix = r'/(?P<model_name>githubrepository|gitlabrepository)',
|
||||
viewset = git_repository.ViewSet,
|
||||
feature_flag = '2025-00001', basename = '_api_gitrepository_sub'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'git_group', viewset = git_group.ViewSet,
|
||||
prefix = '/git_group', viewset = git_group.ViewSet,
|
||||
feature_flag = '2025-00001', basename = '_api_gitgroup'
|
||||
)
|
||||
|
||||
|
@ -11,8 +11,8 @@ app_name = "devops"
|
||||
|
||||
router = SimpleRouter(trailing_slash=False)
|
||||
|
||||
router.register('flags', feature_flag_endpoints.Index, basename='_api_v2_flags')
|
||||
router.register('/flags', feature_flag_endpoints.Index, basename='_api_v2_flags')
|
||||
|
||||
router.register('(?P<organization_id>[0-9]+)/flags/(?P<software_id>[0-9]+)', public_feature_flag.ViewSet, basename='_api_checkin')
|
||||
router.register('/(?P<organization_id>[0-9]+)/flags/(?P<software_id>[0-9]+)', public_feature_flag.ViewSet, basename='_api_checkin')
|
||||
|
||||
urlpatterns = router.urls
|
||||
|
@ -34,57 +34,57 @@ router.register(
|
||||
basename = '_api_v2_itam_home'
|
||||
)
|
||||
router.register(
|
||||
prefix = '(?P<model_name>[itamassetbase]+)', viewset = asset.ViewSet,
|
||||
prefix = '/(?P<model_name>[itamassetbase]+)', viewset = asset.ViewSet,
|
||||
feature_flag = '2025-00007', basename = '_api_itamassetbase'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'device', viewset = device.ViewSet,
|
||||
prefix = '/device', viewset = device.ViewSet,
|
||||
basename = '_api_device'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'device/(?P<device_id>[0-9]+)/operating_system',
|
||||
prefix = '/device/(?P<device_id>[0-9]+)/operating_system',
|
||||
viewset = device_operating_system.ViewSet,
|
||||
basename = '_api_deviceoperatingsystem')
|
||||
router.register(
|
||||
prefix = 'device/(?P<device_id>[0-9]+)/software', viewset = device_software_v2.ViewSet,
|
||||
prefix = '/device/(?P<device_id>[0-9]+)/software', viewset = device_software_v2.ViewSet,
|
||||
basename = '_api_devicesoftware'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'device/(?P<device_id>[0-9]+)/service', viewset = service_device_v2.ViewSet,
|
||||
prefix = '/device/(?P<device_id>[0-9]+)/service', viewset = service_device_v2.ViewSet,
|
||||
basename = '_api_v2_service_device'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'inventory', viewset = inventory.ViewSet,
|
||||
prefix = '/inventory', viewset = inventory.ViewSet,
|
||||
basename = '_api_v2_inventory'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'operating_system', viewset = operating_system_v2.ViewSet,
|
||||
prefix = '/operating_system', viewset = operating_system_v2.ViewSet,
|
||||
basename = '_api_operatingsystem'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'operating_system/(?P<operating_system_id>[0-9]+)/installs',
|
||||
prefix = '/operating_system/(?P<operating_system_id>[0-9]+)/installs',
|
||||
viewset = device_operating_system.ViewSet,
|
||||
basename = '_api_v2_operating_system_installs'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'operating_system/(?P<operating_system_id>[0-9]+)/version',
|
||||
prefix = '/operating_system/(?P<operating_system_id>[0-9]+)/version',
|
||||
viewset = operating_system_version_v2.ViewSet,
|
||||
basename = '_api_operatingsystemversion'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'software', viewset = software_v2.ViewSet,
|
||||
prefix = '/software', viewset = software_v2.ViewSet,
|
||||
basename = '_api_software'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'software/(?P<software_id>[0-9]+)/installs', viewset = device_software_v2.ViewSet,
|
||||
prefix = '/software/(?P<software_id>[0-9]+)/installs', viewset = device_software_v2.ViewSet,
|
||||
basename = '_api_v2_software_installs'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'software/(?P<software_id>[0-9]+)/version', viewset = software_version_v2.ViewSet,
|
||||
prefix = '/software/(?P<software_id>[0-9]+)/version', viewset = software_version_v2.ViewSet,
|
||||
basename = '_api_softwareversion'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'software/(?P<software_id>[0-9]+)/feature_flag',
|
||||
prefix = '/software/(?P<software_id>[0-9]+)/feature_flag',
|
||||
viewset = software_enable_feature_flag.ViewSet,
|
||||
basename = '_api_softwareenablefeatureflag'
|
||||
)
|
||||
|
@ -23,27 +23,27 @@ router.register(
|
||||
basename = '_api_v2_itim_home'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'ticket/change', viewset = change.ViewSet,
|
||||
prefix = '/ticket/change', viewset = change.ViewSet,
|
||||
basename = '_api_v2_ticket_change'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'cluster', viewset = cluster_v2.ViewSet,
|
||||
prefix = '/cluster', viewset = cluster_v2.ViewSet,
|
||||
basename = '_api_cluster'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'cluster/(?P<cluster_id>[0-9]+)/service', viewset = service_cluster.ViewSet,
|
||||
prefix = '/cluster/(?P<cluster_id>[0-9]+)/service', viewset = service_cluster.ViewSet,
|
||||
basename = '_api_v2_service_cluster'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'ticket/incident', viewset = incident.ViewSet,
|
||||
prefix = '/ticket/incident', viewset = incident.ViewSet,
|
||||
basename = '_api_v2_ticket_incident'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'ticket/problem', viewset = problem.ViewSet,
|
||||
prefix = '/ticket/problem', viewset = problem.ViewSet,
|
||||
basename = '_api_v2_ticket_problem'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'service', viewset = service.ViewSet,
|
||||
prefix = '/service', viewset = service.ViewSet,
|
||||
basename = '_api_service'
|
||||
)
|
||||
|
||||
|
@ -64,7 +64,7 @@ class ProjectMilestoneSerializerTestCases(
|
||||
|
||||
|
||||
def test_serializer_validation_no_name(self,
|
||||
kwargs_api_create, model, model_serializer, request_user
|
||||
kwargs_api_create, model, model_serializer, request_user, model_kwargs
|
||||
):
|
||||
"""Serializer Validation Check
|
||||
|
||||
|
@ -20,16 +20,16 @@ router.register(
|
||||
basename = '_api_v2_project_management_home'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'project', viewset = project.ViewSet,
|
||||
prefix = '/project', viewset = project.ViewSet,
|
||||
basename = '_api_project'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'project/(?P<project_id>[0-9]+)/milestone',
|
||||
prefix = '/project/(?P<project_id>[0-9]+)/milestone',
|
||||
viewset = project_milestone.ViewSet,
|
||||
basename = '_api_projectmilestone'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'project/(?P<project_id>[0-9]+)/project_task',
|
||||
prefix = '/project/(?P<project_id>[0-9]+)/project_task',
|
||||
viewset = project_task.ViewSet,
|
||||
basename = '_api_v2_ticket_project_task'
|
||||
)
|
||||
|
@ -52,70 +52,70 @@ router.register(
|
||||
basename = '_api_v2_settings_home'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'app_settings', viewset = app_settings.ViewSet,
|
||||
prefix = '/app_settings', viewset = app_settings.ViewSet,
|
||||
basename = '_api_appsettings'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'celery_log', viewset = celery_log_v2.ViewSet,
|
||||
prefix = '/celery_log', viewset = celery_log_v2.ViewSet,
|
||||
basename = '_api_v2_celery_log'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'cluster_type', viewset = cluster_type_v2.ViewSet,
|
||||
prefix = '/cluster_type', viewset = cluster_type_v2.ViewSet,
|
||||
basename = '_api_clustertype'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'device_model', viewset = device_model.ViewSet,
|
||||
prefix = '/device_model', viewset = device_model.ViewSet,
|
||||
basename = '_api_devicemodel'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'device_type', viewset = device_type.ViewSet,
|
||||
prefix = '/device_type', viewset = device_type.ViewSet,
|
||||
basename = '_api_devicetype'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'external_link', viewset = external_link.ViewSet,
|
||||
prefix = '/external_link', viewset = external_link.ViewSet,
|
||||
basename = '_api_externallink'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'knowledge_base_category',
|
||||
prefix = '/knowledge_base_category',
|
||||
viewset = knowledge_base_category_v2.ViewSet,
|
||||
basename = '_api_knowledgebasecategory'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'manufacturer', viewset = manufacturer_v2.ViewSet,
|
||||
prefix = '/manufacturer', viewset = manufacturer_v2.ViewSet,
|
||||
basename = '_api_manufacturer'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'port', viewset = port_v2.ViewSet,
|
||||
prefix = '/port', viewset = port_v2.ViewSet,
|
||||
basename = '_api_port'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'project_state',
|
||||
prefix = '/project_state',
|
||||
viewset = project_state.ViewSet,
|
||||
basename = '_api_projectstate'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'project_type', viewset = project_type.ViewSet,
|
||||
prefix = '/project_type', viewset = project_type.ViewSet,
|
||||
basename = '_api_projecttype'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'software_category', viewset = software_category_v2.ViewSet,
|
||||
prefix = '/software_category', viewset = software_category_v2.ViewSet,
|
||||
basename = '_api_softwarecategory'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'ticket_category',
|
||||
prefix = '/ticket_category',
|
||||
viewset = ticket_category.ViewSet, basename = '_api_ticketcategory'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'ticket_comment_category',
|
||||
prefix = '/ticket_comment_category',
|
||||
viewset = ticket_comment_category.ViewSet,
|
||||
basename = '_api_ticketcommentcategory'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'user_settings', viewset = user_settings.ViewSet,
|
||||
prefix = '/user_settings', viewset = user_settings.ViewSet,
|
||||
basename = '_api_usersettings'
|
||||
)
|
||||
router.register(
|
||||
prefix = 'user_(?P<model_id>[0-9]+)/token', viewset = auth_token.ViewSet,
|
||||
prefix = '/user_(?P<model_id>[0-9]+)/token', viewset = auth_token.ViewSet,
|
||||
basename = '_api_authtoken'
|
||||
)
|
||||
|
||||
|
8
app/tests/fixtures/model_projectmilestone.py
vendored
8
app/tests/fixtures/model_projectmilestone.py
vendored
@ -75,9 +75,13 @@ def kwargs_projectmilestone(django_db_blocker,
|
||||
|
||||
yield kwargs.copy()
|
||||
|
||||
# with django_db_blocker.unblock():
|
||||
with django_db_blocker.unblock():
|
||||
|
||||
for proj in project.projectmilestone_set.all():
|
||||
proj.delete()
|
||||
|
||||
project.delete()
|
||||
|
||||
# project.delete() # milestone is cascade delete
|
||||
|
||||
|
||||
@pytest.fixture( scope = 'class')
|
||||
|
9
app/tests/fixtures/model_projectstate.py
vendored
9
app/tests/fixtures/model_projectstate.py
vendored
@ -48,10 +48,11 @@ def kwargs_projectstate(kwargs_centurionmodel, django_db_blocker,
|
||||
|
||||
with django_db_blocker.unblock():
|
||||
|
||||
try:
|
||||
runbook.delete()
|
||||
except models.deletion.ProtectedError:
|
||||
pass
|
||||
for proj in runbook.projectstate_set.all():
|
||||
proj.delete()
|
||||
|
||||
runbook.delete()
|
||||
|
||||
|
||||
|
||||
@pytest.fixture( scope = 'class')
|
||||
|
9
app/tests/fixtures/model_projecttype.py
vendored
9
app/tests/fixtures/model_projecttype.py
vendored
@ -47,10 +47,11 @@ def kwargs_projecttype(kwargs_centurionmodel, django_db_blocker,
|
||||
|
||||
with django_db_blocker.unblock():
|
||||
|
||||
try:
|
||||
runbook.delete()
|
||||
except models.deletion.ProtectedError:
|
||||
pass
|
||||
for proj in runbook.projecttype_set.all():
|
||||
proj.delete()
|
||||
|
||||
runbook.delete()
|
||||
|
||||
|
||||
|
||||
@pytest.fixture( scope = 'class')
|
||||
|
@ -159,7 +159,7 @@ EXPOSE 8000
|
||||
VOLUME [ "/data", "/etc/itsm" ]
|
||||
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 CMD \
|
||||
HEALTHCHECK --interval=10s --timeout=30s --start-period=30s --retries=3 CMD \
|
||||
supervisorctl status || exit 1
|
||||
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@ -3,6 +3,7 @@
|
||||
set -e
|
||||
|
||||
mkdir -p /etc/supervisor/conf.d;
|
||||
mkdir -p /var/log/nginx;
|
||||
|
||||
if [ "$1" == "" ]; then
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import coverage
|
||||
import logging
|
||||
import os
|
||||
|
||||
@ -9,6 +10,32 @@ from prometheus_client import multiprocess, start_http_server, REGISTRY
|
||||
|
||||
|
||||
|
||||
if bool(os.environ.get("IS_TESTING")):
|
||||
|
||||
def post_fork(server, worker):
|
||||
|
||||
worker_cov = coverage.Coverage(data_file=f"artifacts/.coverage.{os.getpid()}")
|
||||
|
||||
worker_cov.start()
|
||||
|
||||
worker.worker_cov = worker_cov
|
||||
|
||||
|
||||
def worker_exit(server, worker):
|
||||
|
||||
if hasattr(worker, "worker_cov"):
|
||||
|
||||
worker.worker_cov.stop()
|
||||
|
||||
worker.worker_cov.save()
|
||||
|
||||
|
||||
post_fork = post_fork
|
||||
|
||||
worker_exit = worker_exit
|
||||
|
||||
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'centurion.settings')
|
||||
|
||||
access_logfile = '-'
|
||||
@ -21,9 +48,14 @@ forwarder_headers = "X-REAL-IP,X-FORWARDED-FOR,X-FORWARDED-PROTO"
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
preload_app = False
|
||||
max_requests = 100
|
||||
max_requests_jitter = 30
|
||||
|
||||
workers = 10
|
||||
preload_app = True
|
||||
|
||||
timeout = 180
|
||||
|
||||
workers = 4
|
||||
|
||||
|
||||
def when_ready(_):
|
||||
@ -46,7 +78,8 @@ def when_ready(_):
|
||||
proc_path = os.environ["PROMETHEUS_MULTIPROC_DIR"]
|
||||
|
||||
|
||||
logger.info(f'Setting up prometheus metrics HTTP server on port {str(settings.METRICS_EXPORT_PORT)}.')
|
||||
logger.info(f'Setting up prometheus metrics HTTP server on port \
|
||||
{str(settings.METRICS_EXPORT_PORT)}.')
|
||||
|
||||
multiproc_folder_path = _setup_multiproc_folder()
|
||||
|
||||
|
@ -7,4 +7,4 @@ autorestart=true
|
||||
stdout_logfile=/var/log/%(program_name)s.log
|
||||
stderr_logfile=/var/log/%(program_name)s.log
|
||||
directory=/app
|
||||
command=gunicorn --config=/etc/gunicorn.conf.py centurion.wsgi:application
|
||||
command=gunicorn --config=/etc/gunicorn.conf.py --pid=/run/gunicorn.pid centurion.wsgi:application
|
||||
|
64
makefile
64
makefile
@ -69,6 +69,70 @@ lint: markdown-mkdocs-lint
|
||||
test:
|
||||
pytest --cov-report xml:artifacts/coverage_unit_functional.xml --cov-report html:artifacts/coverage/unit_functional/ --junit-xml=artifacts/unit_functional.JUnit.xml app/**/tests/unit app/**/tests/functional
|
||||
|
||||
|
||||
|
||||
test-integration:
|
||||
export exit_code=0;
|
||||
cp pyproject.toml app/;
|
||||
sed -i 's|^source = \[ "./app" \]|source = [ "." ]|' app/pyproject.toml;
|
||||
cd test;
|
||||
if docker-compose up -d; then
|
||||
|
||||
docker ps -a;
|
||||
|
||||
chmod +x setup-integration.sh;
|
||||
|
||||
if ./setup-integration.sh; then
|
||||
|
||||
cd ..;
|
||||
|
||||
ls -laR test/;
|
||||
|
||||
docker exec -i centurion-erp supervisorctl stop gunicorn;
|
||||
docker exec -i centurion-erp sh -c 'rm -rf /app/artifacts/* /app/artifacts/.[!.]*';
|
||||
docker exec -i centurion-erp supervisorctl start gunicorn;
|
||||
sleep 60;
|
||||
docker ps -a;
|
||||
curl --trace-ascii - http://localhost:8003/api;
|
||||
echo '--------------------------------------------------------------------';
|
||||
curl --trace-ascii - http://127.0.0.1:8003/api;
|
||||
|
||||
|
||||
if [ "0${GITHUB_SHA}"!="0" ]; then
|
||||
|
||||
sudo chmod 777 -R ./test
|
||||
|
||||
fi;
|
||||
|
||||
docker logs centurion-erp;
|
||||
pytest --override-ini addopts= --no-migrations --tb=long --verbosity=2 --full-trace --showlocals --junit-xml=integration.JUnit.xml app/*/tests/integration;
|
||||
docker exec -i centurion-erp supervisorctl restart gunicorn;
|
||||
docker exec -i centurion-erp sh -c 'coverage combine; coverage report --skip-covered; coverage html -d artifacts/html/;';
|
||||
docker logs centurion-erp-init > ./test/volumes/log/docker-log-centurion-erp-init.log;
|
||||
docker logs centurion-erp> ./test/volumes/log/docker-log-centurion-erp.log;
|
||||
docker logs postgres > ./test/volumes/log/docker-log-postgres.log;
|
||||
docker logs rabbitmq > ./test/volumes/log/docker-log-rabbitmq.log;
|
||||
cd test;
|
||||
|
||||
else
|
||||
|
||||
echo 'Error: could not setup containers for testing';
|
||||
export exit_code=10;
|
||||
|
||||
fi;
|
||||
else
|
||||
|
||||
echo 'Error: Failed to launch containers';
|
||||
export exit_code=20;
|
||||
|
||||
fi;
|
||||
cd test;
|
||||
docker-compose down -v;
|
||||
cd ..;
|
||||
exit ${exit_code};
|
||||
|
||||
|
||||
|
||||
test-functional:
|
||||
pytest --cov-report xml:artifacts/coverage_functional.xml --cov-report html:artifacts/coverage/functional/ --junit-xml=artifacts/functional.JUnit.xml app/**/tests/functional
|
||||
|
||||
|
@ -1100,6 +1100,7 @@ markers = [
|
||||
"audit_models: Selects Audit models.",
|
||||
"centurion_models: Selects Centurion models",
|
||||
"functional: Selects all Functional tests.",
|
||||
"integration: Selects all Integration tests.",
|
||||
"meta_models: Selects Meta models",
|
||||
"mixin: Selects all mixin test cases.",
|
||||
"mixin_centurion: Selects all centurion mixin test cases.",
|
||||
|
89
test/docker-compose.yaml
Normal file
89
test/docker-compose.yaml
Normal file
@ -0,0 +1,89 @@
|
||||
---
|
||||
|
||||
x-app: ¢urion
|
||||
image: ${CENTURION_IMAGE:-ghcr.io/nofusscomputing/centurion-erp}:${CENTURION_IMAGE_TAG:-dev}
|
||||
volumes:
|
||||
- ./docker/settings.py:/etc/itsm/settings.py:ro
|
||||
|
||||
|
||||
services:
|
||||
|
||||
|
||||
postgres:
|
||||
image: ${CENTURION_POSTGRES_IMAGE:-postgres}:${CENTURION_POSTGRES_IMAGE_TAG:-13.21}-alpine # 14.18-alpine, 15.13-alpine, 16.9-alpine, 17.5-alpine
|
||||
container_name: postgres
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_USER: admin
|
||||
POSTGRES_PASSWORD: admin
|
||||
expose:
|
||||
- 5432
|
||||
volumes:
|
||||
- ./docker/centurion.sql:/docker-entrypoint-initdb.d/centurion.sql:ro
|
||||
|
||||
|
||||
rabbitmq:
|
||||
image: ${CENTURION_RABBITMQ_IMAGE:-rabbitmq}:${CENTURION_RABBITMQ_IMAGE_TAG:-4.0.9}-management-alpine # 4.1.3-management-alpine
|
||||
container_name: rabbitmq
|
||||
environment:
|
||||
- RABBITMQ_DEFAULT_USER=admin
|
||||
- RABBITMQ_DEFAULT_PASS=admin
|
||||
- RABBITMQ_DEFAULT_VHOST=itsm
|
||||
expose:
|
||||
- 5672
|
||||
ports:
|
||||
# - "5672:5672"
|
||||
- "15672:15672"
|
||||
|
||||
|
||||
centurion-init:
|
||||
<<: *centurion
|
||||
container_name: centurion-erp-init
|
||||
restart: "no"
|
||||
entrypoint: ""
|
||||
command: sh -c 'sleep 15; python manage.py migrate'
|
||||
depends_on:
|
||||
- postgres
|
||||
- rabbitmq
|
||||
|
||||
|
||||
centurion:
|
||||
<<: *centurion
|
||||
container_name: centurion-erp
|
||||
restart: always
|
||||
hostname: centurion-erp
|
||||
volumes:
|
||||
- ./volumes/log:/var/log:rw
|
||||
- ./docker/settings.py:/etc/itsm/settings.py:ro
|
||||
- ./volumes/data:/data:rw
|
||||
- ./volumes/artifacts:/app/artifacts:rw
|
||||
ports:
|
||||
- "8003:8000"
|
||||
depends_on:
|
||||
- postgres
|
||||
- rabbitmq
|
||||
|
||||
|
||||
worker:
|
||||
<<: *centurion
|
||||
container_name: centurion-worker
|
||||
restart: always
|
||||
environment:
|
||||
- IS_WORKER=true
|
||||
hostname: centurion-worker
|
||||
depends_on:
|
||||
- postgres
|
||||
- rabbitmq
|
||||
- centurion
|
||||
|
||||
centurion-ui:
|
||||
image: ${CENTURION_UI_IMAGE:-ghcr.io/nofusscomputing/centurion-erp-ui}:${CENTURION_UI_IMAGE_TAG:-dev}
|
||||
container_name: centurion-ui
|
||||
restart: always
|
||||
environment:
|
||||
- API_URL=http://127.0.0.1:8003/api/v2
|
||||
hostname: centurion-ui
|
||||
ports:
|
||||
- "3000:80"
|
||||
depends_on:
|
||||
- centurion
|
2
test/docker/centurion.sql
Executable file
2
test/docker/centurion.sql
Executable file
@ -0,0 +1,2 @@
|
||||
CREATE DATABASE itsm;
|
||||
GRANT ALL PRIVILEGES ON DATABASE itsm TO admin;
|
79
test/docker/settings.py
Executable file
79
test/docker/settings.py
Executable file
@ -0,0 +1,79 @@
|
||||
# ITSM Docker Settings
|
||||
|
||||
# If metrics enabled, see https://nofusscomputing.com/projects/centurion_erp/administration/monitoring/#django-exporter-setup)
|
||||
# to configure the database metrics.
|
||||
|
||||
API_TEST = True
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = []
|
||||
|
||||
CELERY_BROKER_URL = 'amqp://admin:admin@rabbitmq:5672/itsm' # 'amqp://' is the connection protocol
|
||||
|
||||
CORS_ALLOW_CREDENTIALS = True
|
||||
|
||||
CORS_ALLOW_METHODS = (
|
||||
"DELETE",
|
||||
"GET",
|
||||
"OPTIONS",
|
||||
"PATCH",
|
||||
"POST",
|
||||
"PUT",
|
||||
)
|
||||
|
||||
CORS_ALLOWED_ORIGINS = [
|
||||
"http://127.0.0.1:3000",
|
||||
"http://localhost:3000",
|
||||
"http://127.0.0.1:8003",
|
||||
"http://localhost:8003",
|
||||
"http://127.0.0.1",
|
||||
]
|
||||
|
||||
CORS_EXPOSE_HEADERS = ['Content-Type', 'X-CSRFToken']
|
||||
|
||||
CSRF_COOKIE_SECURE = False
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'NAME': 'itsm',
|
||||
'USER': 'admin',
|
||||
'PASSWORD': 'admin',
|
||||
'HOST': 'postgres',
|
||||
'PORT': '5432',
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG = True
|
||||
|
||||
FEATURE_FLAGGING_ENABLED = True # Turn Feature Flagging on/off
|
||||
|
||||
FEATURE_FLAG_OVERRIDES = [] # Feature Flag Overrides. Takes preceedence over downloaded feature flags.
|
||||
|
||||
LOG_FILES = { # Location where log files will be created
|
||||
"centurion": "/var/log/centurion.log",
|
||||
"weblog": "/var/log/weblog.log",
|
||||
"rest_api": "/var/log/rest_api.log",
|
||||
"catch_all":"/var/log/catch-all.log"
|
||||
}
|
||||
|
||||
METRICS_ENABLED = True
|
||||
|
||||
SECRET_KEY = 'django-insecure-b*41-$afq0yl)1e#qpz^-nbt-opvjwb#avv++b9rfdxa@b55sk'
|
||||
|
||||
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
||||
|
||||
SECURE_SSL_REDIRECT = False
|
||||
|
||||
SESSION_COOKIE_SECURE = False
|
||||
|
||||
SITE_URL = 'http://127.0.0.1:8003'
|
||||
|
||||
TRUSTED_ORIGINS = [
|
||||
"http://127.0.0.1:3000",
|
||||
"http://localhost:3000",
|
||||
"http://127.0.0.1:8003",
|
||||
"http://localhost:8003",
|
||||
"http://127.0.0.1",
|
||||
]
|
||||
|
||||
USE_X_FORWARDED_HOST = True
|
0
test/page_speed.js
Normal file → Executable file
0
test/page_speed.js
Normal file → Executable file
0
test/parameterizedData.json
Normal file → Executable file
0
test/parameterizedData.json
Normal file → Executable file
70
test/setup-integration.sh
Executable file
70
test/setup-integration.sh
Executable file
@ -0,0 +1,70 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
docker exec -i centurion-erp pip install -r /requirements_test.txt
|
||||
|
||||
docker exec -i centurion-erp supervisorctl restart gunicorn
|
||||
|
||||
|
||||
CONTAINER_NAME="centurion-erp-init"
|
||||
TIMEOUT=400
|
||||
INTERVAL=5
|
||||
ELAPSED=0
|
||||
STATUS=""
|
||||
|
||||
while [ "$STATUS" != "exited" ] && [ "$STATUS" != "dead" ]; do
|
||||
|
||||
STATUS=$(docker inspect --format '{{.State.Status}}' "$CONTAINER_NAME" 2>/dev/null || echo "not_found")
|
||||
|
||||
|
||||
if [ "$STATUS" = "not_found" ]; then
|
||||
docker ps -a
|
||||
echo "Container $CONTAINER_NAME was not found."
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [ $ELAPSED -ge $TIMEOUT ]; then
|
||||
echo "Timeout reached. Container $CONTAINER_NAME still running (status: $STATUS)."
|
||||
exit 3
|
||||
fi
|
||||
|
||||
echo "Waiting for container $CONTAINER_NAME to complete... Current status: $STATUS"
|
||||
sleep $INTERVAL
|
||||
ELAPSED=$((ELAPSED + INTERVAL))
|
||||
done
|
||||
|
||||
echo "Container $CONTAINER_NAME has completed."
|
||||
|
||||
|
||||
CONTAINER_NAME="centurion-erp"
|
||||
TIMEOUT=90
|
||||
INTERVAL=5
|
||||
ELAPSED=0
|
||||
STATUS=""
|
||||
|
||||
while [ "$STATUS" != "healthy" ]; do
|
||||
STATUS=$(docker inspect --format '{{.State.Health.Status}}' "$CONTAINER_NAME" 2>/dev/null || echo "none")
|
||||
|
||||
if [ $ELAPSED -ge $TIMEOUT ]; then
|
||||
echo "Timeout reached. Container $CONTAINER_NAME is not healthy."
|
||||
exit 4
|
||||
fi
|
||||
|
||||
echo "Waiting for container $CONTAINER_NAME to be healthy... Current status: $STATUS"
|
||||
sleep $INTERVAL
|
||||
ELAPSED=$((ELAPSED + INTERVAL))
|
||||
done
|
||||
|
||||
docker exec -i centurion-erp python manage.py createsuperuser --username admin --email admin@localhost --noinput
|
||||
|
||||
docker exec -i centurion-erp apk add expect
|
||||
|
||||
docker exec -i centurion-erp expect -c "
|
||||
spawn python manage.py changepassword admin
|
||||
expect \"Password:\"
|
||||
send \"admin\r\"
|
||||
expect \"Password (again):\"
|
||||
send \"admin\r\"
|
||||
expect eof
|
||||
"
|
Reference in New Issue
Block a user