django-nativemojo 0.1.10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_nativemojo-0.1.10.dist-info/LICENSE +19 -0
- django_nativemojo-0.1.10.dist-info/METADATA +96 -0
- django_nativemojo-0.1.10.dist-info/NOTICE +8 -0
- django_nativemojo-0.1.10.dist-info/RECORD +194 -0
- django_nativemojo-0.1.10.dist-info/WHEEL +4 -0
- mojo/__init__.py +3 -0
- mojo/apps/account/__init__.py +1 -0
- mojo/apps/account/admin.py +91 -0
- mojo/apps/account/apps.py +16 -0
- mojo/apps/account/migrations/0001_initial.py +77 -0
- mojo/apps/account/migrations/0002_user_is_email_verified_user_is_phone_verified.py +23 -0
- mojo/apps/account/migrations/0003_group_mojo_secrets_user_mojo_secrets.py +23 -0
- mojo/apps/account/migrations/__init__.py +0 -0
- mojo/apps/account/models/__init__.py +3 -0
- mojo/apps/account/models/group.py +98 -0
- mojo/apps/account/models/member.py +95 -0
- mojo/apps/account/models/pkey.py +18 -0
- mojo/apps/account/models/user.py +211 -0
- mojo/apps/account/rest/__init__.py +3 -0
- mojo/apps/account/rest/group.py +25 -0
- mojo/apps/account/rest/user.py +47 -0
- mojo/apps/account/utils/__init__.py +0 -0
- mojo/apps/account/utils/jwtoken.py +72 -0
- mojo/apps/account/utils/passkeys.py +54 -0
- mojo/apps/fileman/README.md +549 -0
- mojo/apps/fileman/__init__.py +0 -0
- mojo/apps/fileman/apps.py +15 -0
- mojo/apps/fileman/backends/__init__.py +117 -0
- mojo/apps/fileman/backends/base.py +319 -0
- mojo/apps/fileman/backends/filesystem.py +397 -0
- mojo/apps/fileman/backends/s3.py +398 -0
- mojo/apps/fileman/examples/configurations.py +378 -0
- mojo/apps/fileman/examples/usage_example.py +665 -0
- mojo/apps/fileman/management/__init__.py +1 -0
- mojo/apps/fileman/management/commands/__init__.py +1 -0
- mojo/apps/fileman/management/commands/cleanup_expired_uploads.py +222 -0
- mojo/apps/fileman/models/__init__.py +7 -0
- mojo/apps/fileman/models/file.py +292 -0
- mojo/apps/fileman/models/manager.py +227 -0
- mojo/apps/fileman/models/render.py +0 -0
- mojo/apps/fileman/rest/__init__ +0 -0
- mojo/apps/fileman/rest/__init__.py +23 -0
- mojo/apps/fileman/rest/fileman.py +13 -0
- mojo/apps/fileman/rest/upload.py +92 -0
- mojo/apps/fileman/utils/__init__.py +19 -0
- mojo/apps/fileman/utils/upload.py +616 -0
- mojo/apps/incident/__init__.py +1 -0
- mojo/apps/incident/handlers/__init__.py +3 -0
- mojo/apps/incident/handlers/event_handlers.py +142 -0
- mojo/apps/incident/migrations/0001_initial.py +83 -0
- mojo/apps/incident/migrations/0002_rename_bundle_ruleset_bundle_minutes_event_hostname_and_more.py +44 -0
- mojo/apps/incident/migrations/0003_alter_event_model_id.py +18 -0
- mojo/apps/incident/migrations/0004_alter_incident_model_id.py +18 -0
- mojo/apps/incident/migrations/__init__.py +0 -0
- mojo/apps/incident/models/__init__.py +3 -0
- mojo/apps/incident/models/event.py +135 -0
- mojo/apps/incident/models/incident.py +33 -0
- mojo/apps/incident/models/rule.py +247 -0
- mojo/apps/incident/parsers/__init__.py +0 -0
- mojo/apps/incident/parsers/ossec/__init__.py +1 -0
- mojo/apps/incident/parsers/ossec/core.py +82 -0
- mojo/apps/incident/parsers/ossec/parsed.py +23 -0
- mojo/apps/incident/parsers/ossec/rules.py +124 -0
- mojo/apps/incident/parsers/ossec/utils.py +169 -0
- mojo/apps/incident/reporter.py +42 -0
- mojo/apps/incident/rest/__init__.py +2 -0
- mojo/apps/incident/rest/event.py +23 -0
- mojo/apps/incident/rest/ossec.py +22 -0
- mojo/apps/logit/__init__.py +0 -0
- mojo/apps/logit/admin.py +37 -0
- mojo/apps/logit/migrations/0001_initial.py +32 -0
- mojo/apps/logit/migrations/0002_log_duid_log_payload_log_username.py +28 -0
- mojo/apps/logit/migrations/0003_log_level.py +18 -0
- mojo/apps/logit/migrations/__init__.py +0 -0
- mojo/apps/logit/models/__init__.py +1 -0
- mojo/apps/logit/models/log.py +57 -0
- mojo/apps/logit/rest.py +9 -0
- mojo/apps/metrics/README.md +79 -0
- mojo/apps/metrics/__init__.py +12 -0
- mojo/apps/metrics/redis_metrics.py +331 -0
- mojo/apps/metrics/rest/__init__.py +1 -0
- mojo/apps/metrics/rest/base.py +152 -0
- mojo/apps/metrics/rest/db.py +0 -0
- mojo/apps/metrics/utils.py +227 -0
- mojo/apps/notify/README.md +91 -0
- mojo/apps/notify/README_NOTIFICATIONS.md +566 -0
- mojo/apps/notify/__init__.py +0 -0
- mojo/apps/notify/admin.py +52 -0
- mojo/apps/notify/handlers/__init__.py +0 -0
- mojo/apps/notify/handlers/example_handlers.py +516 -0
- mojo/apps/notify/handlers/ses/__init__.py +25 -0
- mojo/apps/notify/handlers/ses/bounce.py +0 -0
- mojo/apps/notify/handlers/ses/complaint.py +25 -0
- mojo/apps/notify/handlers/ses/message.py +86 -0
- mojo/apps/notify/management/__init__.py +0 -0
- mojo/apps/notify/management/commands/__init__.py +1 -0
- mojo/apps/notify/management/commands/process_notifications.py +370 -0
- mojo/apps/notify/mod +0 -0
- mojo/apps/notify/models/__init__.py +12 -0
- mojo/apps/notify/models/account.py +128 -0
- mojo/apps/notify/models/attachment.py +24 -0
- mojo/apps/notify/models/bounce.py +68 -0
- mojo/apps/notify/models/complaint.py +40 -0
- mojo/apps/notify/models/inbox.py +113 -0
- mojo/apps/notify/models/inbox_message.py +173 -0
- mojo/apps/notify/models/outbox.py +129 -0
- mojo/apps/notify/models/outbox_message.py +288 -0
- mojo/apps/notify/models/template.py +30 -0
- mojo/apps/notify/providers/__init__.py +0 -0
- mojo/apps/notify/providers/aws.py +73 -0
- mojo/apps/notify/rest/__init__.py +0 -0
- mojo/apps/notify/rest/ses.py +0 -0
- mojo/apps/notify/utils/__init__.py +2 -0
- mojo/apps/notify/utils/notifications.py +404 -0
- mojo/apps/notify/utils/parsing.py +202 -0
- mojo/apps/notify/utils/render.py +144 -0
- mojo/apps/tasks/README.md +118 -0
- mojo/apps/tasks/__init__.py +11 -0
- mojo/apps/tasks/manager.py +489 -0
- mojo/apps/tasks/rest/__init__.py +2 -0
- mojo/apps/tasks/rest/hooks.py +0 -0
- mojo/apps/tasks/rest/tasks.py +62 -0
- mojo/apps/tasks/runner.py +174 -0
- mojo/apps/tasks/tq_handlers.py +14 -0
- mojo/decorators/__init__.py +3 -0
- mojo/decorators/auth.py +25 -0
- mojo/decorators/cron.py +31 -0
- mojo/decorators/http.py +132 -0
- mojo/decorators/validate.py +14 -0
- mojo/errors.py +88 -0
- mojo/helpers/__init__.py +0 -0
- mojo/helpers/aws/__init__.py +0 -0
- mojo/helpers/aws/client.py +8 -0
- mojo/helpers/aws/s3.py +268 -0
- mojo/helpers/aws/setup_email.py +0 -0
- mojo/helpers/cron.py +79 -0
- mojo/helpers/crypto/__init__.py +4 -0
- mojo/helpers/crypto/aes.py +60 -0
- mojo/helpers/crypto/hash.py +59 -0
- mojo/helpers/crypto/privpub/__init__.py +1 -0
- mojo/helpers/crypto/privpub/hybrid.py +97 -0
- mojo/helpers/crypto/privpub/rsa.py +104 -0
- mojo/helpers/crypto/sign.py +36 -0
- mojo/helpers/crypto/too.l.py +25 -0
- mojo/helpers/crypto/utils.py +26 -0
- mojo/helpers/daemon.py +94 -0
- mojo/helpers/dates.py +69 -0
- mojo/helpers/dns/__init__.py +0 -0
- mojo/helpers/dns/godaddy.py +62 -0
- mojo/helpers/filetypes.py +128 -0
- mojo/helpers/logit.py +310 -0
- mojo/helpers/modules.py +95 -0
- mojo/helpers/paths.py +63 -0
- mojo/helpers/redis.py +10 -0
- mojo/helpers/request.py +89 -0
- mojo/helpers/request_parser.py +269 -0
- mojo/helpers/response.py +14 -0
- mojo/helpers/settings.py +146 -0
- mojo/helpers/sysinfo.py +140 -0
- mojo/helpers/ua.py +0 -0
- mojo/middleware/__init__.py +0 -0
- mojo/middleware/auth.py +26 -0
- mojo/middleware/logging.py +55 -0
- mojo/middleware/mojo.py +21 -0
- mojo/migrations/0001_initial.py +32 -0
- mojo/migrations/__init__.py +0 -0
- mojo/models/__init__.py +2 -0
- mojo/models/meta.py +262 -0
- mojo/models/rest.py +538 -0
- mojo/models/secrets.py +59 -0
- mojo/rest/__init__.py +1 -0
- mojo/rest/info.py +26 -0
- mojo/serializers/__init__.py +0 -0
- mojo/serializers/models.py +165 -0
- mojo/serializers/openapi.py +188 -0
- mojo/urls.py +38 -0
- mojo/ws4redis/README.md +174 -0
- mojo/ws4redis/__init__.py +2 -0
- mojo/ws4redis/client.py +283 -0
- mojo/ws4redis/connection.py +327 -0
- mojo/ws4redis/exceptions.py +32 -0
- mojo/ws4redis/redis.py +183 -0
- mojo/ws4redis/servers/__init__.py +0 -0
- mojo/ws4redis/servers/base.py +86 -0
- mojo/ws4redis/servers/django.py +171 -0
- mojo/ws4redis/servers/uwsgi.py +63 -0
- mojo/ws4redis/settings.py +45 -0
- mojo/ws4redis/utf8validator.py +128 -0
- mojo/ws4redis/websocket.py +403 -0
- testit/__init__.py +0 -0
- testit/client.py +147 -0
- testit/faker.py +20 -0
- testit/helpers.py +198 -0
- testit/runner.py +262 -0
testit/helpers.py
ADDED
@@ -0,0 +1,198 @@
|
|
1
|
+
import sys
|
2
|
+
from objict import objict
|
3
|
+
from mojo.helpers import logit
|
4
|
+
import functools
|
5
|
+
import traceback
|
6
|
+
|
7
|
+
|
8
|
+
TEST_RUN = objict(
|
9
|
+
total=0, passed=0, failed=0,
|
10
|
+
tests=objict(active_test=None),
|
11
|
+
results=objict())
|
12
|
+
STOP_ON_FAIL = True
|
13
|
+
VERBOSE = False
|
14
|
+
INDENT = " "
|
15
|
+
|
16
|
+
|
17
|
+
class TestitAbort(Exception):
|
18
|
+
pass
|
19
|
+
|
20
|
+
|
21
|
+
def _run_setup(func, *args, **kwargs):
|
22
|
+
name = kwargs.get("name", func.__name__)
|
23
|
+
logit.color_print(f"{INDENT}{name.ljust(60, '.')}", logit.ConsoleLogger.PINK, end="")
|
24
|
+
res = func(*args, **kwargs)
|
25
|
+
logit.color_print("DONE", logit.ConsoleLogger.PINK, end="\n")
|
26
|
+
return res
|
27
|
+
|
28
|
+
|
29
|
+
def unit_setup():
|
30
|
+
"""
|
31
|
+
Decorator to mark a function as a test setup function.
|
32
|
+
Will be run before each test in the test class.
|
33
|
+
|
34
|
+
Usage:
|
35
|
+
@unit_setup()
|
36
|
+
def setup():
|
37
|
+
# Setup code here
|
38
|
+
pass
|
39
|
+
"""
|
40
|
+
def decorator(func):
|
41
|
+
@functools.wraps(func)
|
42
|
+
def wrapper(*args, **kwargs):
|
43
|
+
return _run_setup(func, *args, **kwargs)
|
44
|
+
wrapper._is_setup = True
|
45
|
+
return wrapper
|
46
|
+
return decorator
|
47
|
+
|
48
|
+
|
49
|
+
def django_unit_setup():
|
50
|
+
"""
|
51
|
+
Decorator to mark a function as a test setup function.
|
52
|
+
Will be run before each test in the test class.
|
53
|
+
|
54
|
+
Usage:
|
55
|
+
@django_setup()
|
56
|
+
def setup():
|
57
|
+
# Setup code here
|
58
|
+
pass
|
59
|
+
"""
|
60
|
+
def decorator(func):
|
61
|
+
@functools.wraps(func)
|
62
|
+
def wrapper(*args, **kwargs):
|
63
|
+
import os
|
64
|
+
import django
|
65
|
+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings')
|
66
|
+
django.setup()
|
67
|
+
return _run_setup(func, *args, **kwargs)
|
68
|
+
wrapper._is_setup = True
|
69
|
+
return wrapper
|
70
|
+
return decorator
|
71
|
+
|
72
|
+
|
73
|
+
def _run_unit(func, name, *args, **kwargs):
|
74
|
+
TEST_RUN.total += 1
|
75
|
+
if name:
|
76
|
+
test_name = name
|
77
|
+
else:
|
78
|
+
test_name = kwargs.get("test_name", func.__name__)
|
79
|
+
if test_name.startswith("test_"):
|
80
|
+
test_name = test_name[5:]
|
81
|
+
|
82
|
+
# Print test start message
|
83
|
+
logit.color_print(f"{INDENT}{test_name.ljust(60, '.')}", logit.ConsoleLogger.YELLOW, end="")
|
84
|
+
|
85
|
+
try:
|
86
|
+
result = func(*args, **kwargs)
|
87
|
+
TEST_RUN.results[f"{TEST_RUN.active_test}:{test_name}"] = True
|
88
|
+
TEST_RUN.passed += 1
|
89
|
+
|
90
|
+
logit.color_print("PASSED", logit.ConsoleLogger.GREEN, end="\n")
|
91
|
+
return result
|
92
|
+
|
93
|
+
except AssertionError as error:
|
94
|
+
TEST_RUN.failed += 1
|
95
|
+
TEST_RUN.results[f"{TEST_RUN.active_test}:{test_name}"] = False
|
96
|
+
|
97
|
+
# Print failure message
|
98
|
+
logit.color_print("FAILED", logit.ConsoleLogger.RED, end="\n")
|
99
|
+
logit.color_print(f"{INDENT}{INDENT}{error}", logit.ConsoleLogger.PINK)
|
100
|
+
|
101
|
+
if STOP_ON_FAIL:
|
102
|
+
raise TestitAbort()
|
103
|
+
|
104
|
+
except Exception as error:
|
105
|
+
TEST_RUN.failed += 1
|
106
|
+
TEST_RUN.results[f"{TEST_RUN.active_test}:{test_name}"] = False
|
107
|
+
|
108
|
+
# Print error message
|
109
|
+
logit.color_print("FAILED", logit.ConsoleLogger.RED, end="\n")
|
110
|
+
if VERBOSE:
|
111
|
+
logit.color_print(traceback.format_exc(), logit.ConsoleLogger.PINK)
|
112
|
+
if STOP_ON_FAIL:
|
113
|
+
raise TestitAbort()
|
114
|
+
return False
|
115
|
+
|
116
|
+
# Test Decorator
|
117
|
+
def unit_test(name=None):
|
118
|
+
"""
|
119
|
+
Decorator to track unit test execution.
|
120
|
+
|
121
|
+
Usage:
|
122
|
+
@unit_test("Custom Test Name")
|
123
|
+
def my_test():
|
124
|
+
assert 1 == 1
|
125
|
+
"""
|
126
|
+
def decorator(func):
|
127
|
+
@functools.wraps(func)
|
128
|
+
def wrapper(*args, **kwargs):
|
129
|
+
_run_unit(func, name, *args, **kwargs)
|
130
|
+
return wrapper
|
131
|
+
return decorator
|
132
|
+
|
133
|
+
|
134
|
+
# Test Decorator
|
135
|
+
def django_unit_test(name=None):
|
136
|
+
"""
|
137
|
+
Decorator to track unit test execution.
|
138
|
+
|
139
|
+
Usage:
|
140
|
+
@unit_test("Custom Test Name")
|
141
|
+
def my_test():
|
142
|
+
assert 1 == 1
|
143
|
+
"""
|
144
|
+
def decorator(func):
|
145
|
+
@functools.wraps(func)
|
146
|
+
def wrapper(*args, **kwargs):
|
147
|
+
import os
|
148
|
+
import django
|
149
|
+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings')
|
150
|
+
django.setup()
|
151
|
+
_run_unit(func, name, *args, **kwargs)
|
152
|
+
return wrapper
|
153
|
+
return decorator
|
154
|
+
|
155
|
+
|
156
|
+
def get_mock_request(user=None, ip="127.0.0.1", path='/', method='GET', META=None):
|
157
|
+
"""
|
158
|
+
Creates a mock Django request object with a user and request.ip information.
|
159
|
+
|
160
|
+
Args:
|
161
|
+
user (User, optional): A mock user object. Defaults to None.
|
162
|
+
ip (str, optional): The IP address for the request. Defaults to "127.0.0.1".
|
163
|
+
path (str, optional): The path for the request. Defaults to '/'.
|
164
|
+
method (str, optional): The HTTP method for the request. Defaults to 'GET'.
|
165
|
+
META (dict, optional): Additional metadata for the request.
|
166
|
+
Merges with default if provided. Defaults to None.
|
167
|
+
|
168
|
+
Returns:
|
169
|
+
objict: A mock request object with request.ip, request.user, and additional attributes.
|
170
|
+
"""
|
171
|
+
request = objict()
|
172
|
+
request.ip = ip
|
173
|
+
request.user = user if user else get_mock_user()
|
174
|
+
default_META = {
|
175
|
+
'SERVER_PROTOCOL': 'HTTP/1.1',
|
176
|
+
'QUERY_STRING': '',
|
177
|
+
'HTTP_USER_AGENT': 'Mozilla/5.0',
|
178
|
+
'HTTP_HOST': 'localhost',
|
179
|
+
}
|
180
|
+
request.META = {**default_META, **(META or {})}
|
181
|
+
request.method = method
|
182
|
+
request.path = path
|
183
|
+
return request
|
184
|
+
|
185
|
+
def get_mock_user():
|
186
|
+
"""
|
187
|
+
Creates a mock user object.
|
188
|
+
|
189
|
+
Returns:
|
190
|
+
objict: A mock user object with basic attributes.
|
191
|
+
"""
|
192
|
+
user = objict()
|
193
|
+
user.id = 1
|
194
|
+
user.username = "mockuser"
|
195
|
+
user.email = "mockuser@example.com"
|
196
|
+
user.is_authenticated = True
|
197
|
+
user.has_permission = lambda perm: True
|
198
|
+
return user
|
testit/runner.py
ADDED
@@ -0,0 +1,262 @@
|
|
1
|
+
import os
|
2
|
+
import sys
|
3
|
+
import time
|
4
|
+
import traceback
|
5
|
+
import inspect
|
6
|
+
import argparse
|
7
|
+
from importlib import import_module
|
8
|
+
|
9
|
+
from mojo.helpers import logit
|
10
|
+
from testit import helpers
|
11
|
+
import testit.client
|
12
|
+
|
13
|
+
from mojo.helpers import paths
|
14
|
+
TEST_ROOT = paths.APPS_ROOT / "tests"
|
15
|
+
|
16
|
+
def get_host():
|
17
|
+
"""Extract host and port from dev_server.conf."""
|
18
|
+
host = "127.0.0.1"
|
19
|
+
port = 8001
|
20
|
+
try:
|
21
|
+
config_path = paths.CONFIG_ROOT / "dev_server.conf"
|
22
|
+
with open(config_path, 'r') as file:
|
23
|
+
for line in file:
|
24
|
+
if line.startswith("host"):
|
25
|
+
host = line.split('=')[1].strip()
|
26
|
+
elif line.startswith("port"):
|
27
|
+
port = line.split('=')[1].strip()
|
28
|
+
except FileNotFoundError:
|
29
|
+
print("Configuration file not found.")
|
30
|
+
except Exception as e:
|
31
|
+
print(f"Error reading configuration: {e}")
|
32
|
+
return f"http://{host}:{port}"
|
33
|
+
|
34
|
+
def setup_parser():
|
35
|
+
"""Setup command-line arguments for the test runner."""
|
36
|
+
parser = argparse.ArgumentParser(description="Django Test Runner")
|
37
|
+
|
38
|
+
parser.add_argument("-v", "--verbose", action="store_true",
|
39
|
+
help="Enable verbose logging")
|
40
|
+
parser.add_argument("-f", "--force", action="store_true",
|
41
|
+
help="Force the test to run now")
|
42
|
+
parser.add_argument("-u", "--user", type=str, default="nobody",
|
43
|
+
help="Specify the user the test should run as")
|
44
|
+
parser.add_argument("-m", "--module", type=str, default=None,
|
45
|
+
help="Run only this app/module")
|
46
|
+
parser.add_argument("--method", type=str, default=None,
|
47
|
+
help="Run only a specific test method")
|
48
|
+
parser.add_argument("-t", "--test", type=str, default=None,
|
49
|
+
help="Specify a specific test method to run")
|
50
|
+
parser.add_argument("-q", "--quick", action="store_true",
|
51
|
+
help="Run only tests flagged as critical/quick")
|
52
|
+
parser.add_argument("-x", "--extra", type=str, default=None,
|
53
|
+
help="Specify extra data to pass to test")
|
54
|
+
parser.add_argument("-l", "--list", action="store_true",
|
55
|
+
help="List available tests instead of running them")
|
56
|
+
parser.add_argument("-s", "--stop", action="store_true",
|
57
|
+
help="Stop on errors")
|
58
|
+
parser.add_argument("-e", "--errors", action="store_true",
|
59
|
+
help="Show errors")
|
60
|
+
parser.add_argument("--host", type=str, default=get_host(),
|
61
|
+
help="Specify host for API tests")
|
62
|
+
parser.add_argument("--setup", action="store_true",
|
63
|
+
help="Run setup before executing tests")
|
64
|
+
parser.add_argument("--nomojo", action="store_true",
|
65
|
+
help="Do not run Mojo app tests")
|
66
|
+
parser.add_argument("--onlymojo", action="store_true",
|
67
|
+
help="Only run Mojo app tests")
|
68
|
+
return parser.parse_args()
|
69
|
+
|
70
|
+
|
71
|
+
def run_test(opts, module, func_name, module_name, test_name):
|
72
|
+
"""Run a specific test function inside a module."""
|
73
|
+
test_key = f"{module_name}.{test_name}.{func_name}"
|
74
|
+
helpers.VERBOSE = opts.verbose
|
75
|
+
helpers.TEST_RUN.tests.active_test = test_key.replace(".", ":")
|
76
|
+
try:
|
77
|
+
getattr(module, func_name)(opts)
|
78
|
+
except Exception as err:
|
79
|
+
if opts.verbose:
|
80
|
+
print(f"⚠️ Test Error: {err}")
|
81
|
+
if opts.stop:
|
82
|
+
sys.exit(1)
|
83
|
+
|
84
|
+
|
85
|
+
def run_setup(opts, module, func_name, module_name, test_name):
|
86
|
+
"""Run a specific test function inside a module."""
|
87
|
+
test_key = f"{module_name}.{test_name}.{func_name}"
|
88
|
+
helpers.VERBOSE = opts.verbose
|
89
|
+
try:
|
90
|
+
getattr(module, func_name)(opts)
|
91
|
+
except Exception as err:
|
92
|
+
if opts.verbose:
|
93
|
+
print(f"⚠️ Setup Error: {err}")
|
94
|
+
if opts.stop:
|
95
|
+
sys.exit(1)
|
96
|
+
|
97
|
+
|
98
|
+
def import_module_for_testing(module_name, test_name):
|
99
|
+
"""Dynamically import a test module."""
|
100
|
+
try:
|
101
|
+
name = f"{module_name}.{test_name}"
|
102
|
+
module = import_module(name)
|
103
|
+
return module
|
104
|
+
except ImportError:
|
105
|
+
print(f"⚠️ Failed to import test module: {name}")
|
106
|
+
traceback.print_exc()
|
107
|
+
return None
|
108
|
+
|
109
|
+
|
110
|
+
def run_module_tests_by_name(opts, module_name, test_name):
|
111
|
+
"""Run all test functions in a specific test module in the order they appear."""
|
112
|
+
module = import_module_for_testing(module_name, test_name)
|
113
|
+
if not module:
|
114
|
+
return
|
115
|
+
run_module_setup(opts, module, test_name, module_name)
|
116
|
+
run_module_tests(opts, module, test_name, module_name)
|
117
|
+
|
118
|
+
|
119
|
+
def run_module_setup(opts, module, test_name, module_name):
|
120
|
+
opts.client = testit.client.RestClient(opts.host, logger=opts.logger)
|
121
|
+
test_key = f"{module_name}.{test_name}"
|
122
|
+
started = time.time()
|
123
|
+
prefix = "setup_"
|
124
|
+
|
125
|
+
# Get all functions in the module
|
126
|
+
functions = inspect.getmembers(module, inspect.isfunction)
|
127
|
+
|
128
|
+
# Preserve definition order by using inspect.getsourcelines()
|
129
|
+
functions = sorted(
|
130
|
+
functions,
|
131
|
+
key=lambda func: inspect.getsourcelines(func[1])[1] # Sort by line number
|
132
|
+
)
|
133
|
+
setup_funcs = []
|
134
|
+
for func_name, func in functions:
|
135
|
+
if func_name.startswith(prefix):
|
136
|
+
setup_funcs.append((module, func_name))
|
137
|
+
|
138
|
+
if len(setup_funcs):
|
139
|
+
logit.color_print(f"\nRUNNING SETUP: {test_key}", logit.ConsoleLogger.BLUE)
|
140
|
+
for module, func_name in setup_funcs:
|
141
|
+
run_setup(opts, module, func_name, module_name, test_name)
|
142
|
+
duration = time.time() - started
|
143
|
+
print(f"{helpers.INDENT}---------\n{helpers.INDENT}run time: {duration:.2f}s")
|
144
|
+
|
145
|
+
|
146
|
+
def run_module_tests(opts, module, test_name, module_name):
|
147
|
+
opts.client = testit.client.RestClient(opts.host, logger=opts.logger)
|
148
|
+
test_key = f"{module_name}.{test_name}"
|
149
|
+
logit.color_print(f"\nRUNNING TEST: {test_key}", logit.ConsoleLogger.BLUE)
|
150
|
+
started = time.time()
|
151
|
+
prefix = "test_" if not opts.quick else "quick_"
|
152
|
+
|
153
|
+
# Get all functions in the module
|
154
|
+
functions = inspect.getmembers(module, inspect.isfunction)
|
155
|
+
|
156
|
+
# Preserve definition order by using inspect.getsourcelines()
|
157
|
+
functions = sorted(
|
158
|
+
functions,
|
159
|
+
key=lambda func: inspect.getsourcelines(func[1])[1] # Sort by line number
|
160
|
+
)
|
161
|
+
|
162
|
+
for func_name, func in functions:
|
163
|
+
if func_name.startswith(prefix):
|
164
|
+
run_test(opts, module, func_name, module_name, test_name)
|
165
|
+
|
166
|
+
duration = time.time() - started
|
167
|
+
print(f"{helpers.INDENT}---------\n{helpers.INDENT}run time: {duration:.2f}s")
|
168
|
+
|
169
|
+
|
170
|
+
def run_tests_for_module(opts, module_name, test_root, parent_test_root=None):
|
171
|
+
"""Discover and run tests for a given module."""
|
172
|
+
module_path = os.path.join(test_root, module_name)
|
173
|
+
if not os.path.exists(module_path):
|
174
|
+
if parent_test_root is None:
|
175
|
+
raise FileNotFoundError(f"Module '{module_name}' not found")
|
176
|
+
module_path = os.path.join(parent_test_root, module_name)
|
177
|
+
if not os.path.exists(module_path):
|
178
|
+
raise FileNotFoundError(f"Module '{module_name}' not found")
|
179
|
+
test_files = [f for f in os.listdir(module_path)
|
180
|
+
if f.endswith(".py") and f not in ["__init__.py", "setup.py"]]
|
181
|
+
|
182
|
+
for test_file in sorted(test_files):
|
183
|
+
test_name = test_file.rsplit('.', 1)[0] # Remove .py extension
|
184
|
+
run_module_tests_by_name(opts, module_name, test_name)
|
185
|
+
|
186
|
+
|
187
|
+
def setup_modules():
|
188
|
+
"""Run setup scripts for all test modules."""
|
189
|
+
logit.color_print("\n[TEST PREFLIGHT SETUP]\n", logit.ConsoleLogger.BLUE)
|
190
|
+
|
191
|
+
test_modules = [d for d in os.listdir(TEST_ROOT) if os.path.isdir(os.path.join(TEST_ROOT, d))]
|
192
|
+
|
193
|
+
for module_name in sorted(test_modules):
|
194
|
+
setup_file = os.path.join(TEST_ROOT, module_name, "setup.py")
|
195
|
+
if os.path.isfile(setup_file):
|
196
|
+
module = import_module_for_testing(module_name, "setup")
|
197
|
+
if module and hasattr(module, "run_setup"):
|
198
|
+
logit.color_print(f"Setting up {module_name}...", logit.ConsoleLogger.YELLOW)
|
199
|
+
module.run_setup(opts)
|
200
|
+
logit.color_print("✔ DONE\n", logit.ConsoleLogger.GREEN)
|
201
|
+
|
202
|
+
|
203
|
+
def main(opts):
|
204
|
+
"""Main function to run tests."""
|
205
|
+
if opts.setup:
|
206
|
+
setup_modules()
|
207
|
+
|
208
|
+
if opts.list:
|
209
|
+
print("\n------------------------")
|
210
|
+
print("Listing available test modules & tests")
|
211
|
+
print("[module]")
|
212
|
+
print(" [test1]")
|
213
|
+
print(" [test2]")
|
214
|
+
print("------------------------")
|
215
|
+
return
|
216
|
+
|
217
|
+
opts.logger = logit.get_logger("testit", "testit.log")
|
218
|
+
|
219
|
+
if opts.module and '.' in opts.module:
|
220
|
+
opts.module, opts.test = opts.module.split('.', 1)
|
221
|
+
|
222
|
+
parent_test_root = os.path.join(os.path.dirname(os.path.dirname(__file__)), "tests")
|
223
|
+
parent_test_modules = None
|
224
|
+
if os.path.exists(parent_test_root):
|
225
|
+
sys.path.insert(0, parent_test_root)
|
226
|
+
parent_test_modules = sorted([d for d in os.listdir(parent_test_root) if os.path.isdir(os.path.join(parent_test_root, d))])
|
227
|
+
|
228
|
+
if opts.module and opts.test:
|
229
|
+
run_module_tests_by_name(opts, opts.module, opts.test)
|
230
|
+
elif opts.module:
|
231
|
+
run_tests_for_module(opts, opts.module, TEST_ROOT, parent_test_root)
|
232
|
+
else:
|
233
|
+
test_root = os.path.join(paths.APPS_ROOT, "tests")
|
234
|
+
test_modules = sorted([d for d in os.listdir(test_root) if os.path.isdir(os.path.join(test_root, d))])
|
235
|
+
if parent_test_modules and not opts.nomojo:
|
236
|
+
for module_name in parent_test_modules:
|
237
|
+
run_tests_for_module(opts, module_name, parent_test_root)
|
238
|
+
if not opts.onlymojo:
|
239
|
+
for module_name in test_modules:
|
240
|
+
run_tests_for_module(opts, module_name, test_root)
|
241
|
+
|
242
|
+
# Summary Output
|
243
|
+
print("\n" + "=" * 80)
|
244
|
+
|
245
|
+
logit.color_print(f"TOTAL RUN: {helpers.TEST_RUN.total}\t", logit.ConsoleLogger.YELLOW)
|
246
|
+
logit.color_print(f"TOTAL PASSED: {helpers.TEST_RUN.passed}", logit.ConsoleLogger.GREEN)
|
247
|
+
if helpers.TEST_RUN.failed > 0:
|
248
|
+
logit.color_print(f"TOTAL FAILED: {helpers.TEST_RUN.failed}", logit.ConsoleLogger.RED)
|
249
|
+
|
250
|
+
print("=" * 80)
|
251
|
+
|
252
|
+
# Save Test Results
|
253
|
+
helpers.TEST_RUN.save(os.path.join(paths.VAR_ROOT, "test_results.json"))
|
254
|
+
|
255
|
+
# Exit with failure status if any test failed
|
256
|
+
if helpers.TEST_RUN.failed > 0:
|
257
|
+
sys.exit("❌ Tests failed!")
|
258
|
+
|
259
|
+
|
260
|
+
if __name__ == "__main__":
|
261
|
+
opts = setup_parser()
|
262
|
+
main(opts)
|