ivoryos 1.0.5__py3-none-any.whl → 1.0.7__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.
Potentially problematic release.
This version of ivoryos might be problematic. Click here for more details.
- ivoryos/__init__.py +4 -4
- ivoryos/config.py +15 -2
- ivoryos/version.py +1 -1
- {ivoryos-1.0.5.dist-info → ivoryos-1.0.7.dist-info}/METADATA +1 -4
- {ivoryos-1.0.5.dist-info → ivoryos-1.0.7.dist-info}/RECORD +17 -8
- {ivoryos-1.0.5.dist-info → ivoryos-1.0.7.dist-info}/top_level.txt +1 -0
- tests/__init__.py +0 -0
- tests/conftest.py +133 -0
- tests/integration/__init__.py +0 -0
- tests/integration/test_route_auth.py +80 -0
- tests/integration/test_route_control.py +94 -0
- tests/integration/test_route_database.py +61 -0
- tests/integration/test_route_design.py +36 -0
- tests/integration/test_route_main.py +35 -0
- tests/integration/test_sockets.py +26 -0
- {ivoryos-1.0.5.dist-info → ivoryos-1.0.7.dist-info}/LICENSE +0 -0
- {ivoryos-1.0.5.dist-info → ivoryos-1.0.7.dist-info}/WHEEL +0 -0
ivoryos/__init__.py
CHANGED
|
@@ -55,9 +55,9 @@ def create_app(config_class=None):
|
|
|
55
55
|
create app, init database
|
|
56
56
|
"""
|
|
57
57
|
app.config.from_object(config_class or 'config.get_config()')
|
|
58
|
-
|
|
58
|
+
os.makedirs(app.config["OUTPUT_FOLDER"], exist_ok=True)
|
|
59
59
|
# Initialize extensions
|
|
60
|
-
socketio.init_app(app)
|
|
60
|
+
socketio.init_app(app, cors_allowed_origins="*", cookie=None, logger=True, engineio_logger=True)
|
|
61
61
|
login_manager.init_app(app)
|
|
62
62
|
login_manager.login_view = "auth.login"
|
|
63
63
|
db.init_app(app)
|
|
@@ -114,7 +114,6 @@ def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, mod
|
|
|
114
114
|
:param exclude_names: list[str] module names to exclude from parsing
|
|
115
115
|
"""
|
|
116
116
|
app = create_app(config_class=config or get_config()) # Create app instance using factory function
|
|
117
|
-
os.makedirs(app.config["OUTPUT_FOLDER"], exist_ok=True)
|
|
118
117
|
|
|
119
118
|
# plugins = load_installed_plugins(app, socketio)
|
|
120
119
|
plugins = []
|
|
@@ -136,13 +135,14 @@ def run(module=None, host="0.0.0.0", port=None, debug=None, llm_server=None, mod
|
|
|
136
135
|
app.config["LOGGERS"] = logger
|
|
137
136
|
app.config["LOGGERS_PATH"] = logger_output_name or app.config["LOGGERS_PATH"] # default.log
|
|
138
137
|
logger_path = os.path.join(app.config["OUTPUT_FOLDER"], app.config["LOGGERS_PATH"])
|
|
138
|
+
dummy_deck_path = os.path.join(app.config["OUTPUT_FOLDER"], app.config["DUMMY_DECK"])
|
|
139
139
|
|
|
140
140
|
if module:
|
|
141
141
|
app.config["MODULE"] = module
|
|
142
142
|
app.config["OFF_LINE"] = False
|
|
143
143
|
global_config.deck = sys.modules[module]
|
|
144
144
|
global_config.deck_snapshot = utils.create_deck_snapshot(global_config.deck,
|
|
145
|
-
output_path=
|
|
145
|
+
output_path=dummy_deck_path,
|
|
146
146
|
save=True,
|
|
147
147
|
exclude_names=exclude_names
|
|
148
148
|
)
|
ivoryos/config.py
CHANGED
|
@@ -36,12 +36,25 @@ class ProductionConfig(Config):
|
|
|
36
36
|
class TestingConfig(Config):
|
|
37
37
|
DEBUG = True
|
|
38
38
|
TESTING = True
|
|
39
|
+
SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:' # Use an in-memory SQLite database for tests
|
|
40
|
+
WTF_CSRF_ENABLED = False # Disable CSRF for testing forms
|
|
41
|
+
|
|
42
|
+
|
|
39
43
|
|
|
40
44
|
class DemoConfig(Config):
|
|
41
45
|
DEBUG = False
|
|
42
46
|
SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'
|
|
43
|
-
OUTPUT_FOLDER =
|
|
44
|
-
|
|
47
|
+
OUTPUT_FOLDER = os.path.join(os.path.abspath(os.curdir), '/tmp/ivoryos_data')
|
|
48
|
+
CSV_FOLDER = os.path.join(OUTPUT_FOLDER, 'config_csv/')
|
|
49
|
+
SCRIPT_FOLDER = os.path.join(OUTPUT_FOLDER, 'scripts/')
|
|
50
|
+
DATA_FOLDER = os.path.join(OUTPUT_FOLDER, 'results/')
|
|
51
|
+
DUMMY_DECK = os.path.join(OUTPUT_FOLDER, 'pseudo_deck/')
|
|
52
|
+
LLM_OUTPUT = os.path.join(OUTPUT_FOLDER, 'llm_output/')
|
|
53
|
+
DECK_HISTORY = os.path.join(OUTPUT_FOLDER, 'deck_history.txt')
|
|
54
|
+
# session and cookies
|
|
55
|
+
SESSION_COOKIE_SECURE = True
|
|
56
|
+
SESSION_COOKIE_SAMESITE = "None"
|
|
57
|
+
SESSION_COOKIE_HTTPONLY = True
|
|
45
58
|
|
|
46
59
|
def get_config(env='dev'):
|
|
47
60
|
if env == 'production':
|
ivoryos/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "1.0.
|
|
1
|
+
__version__ = "1.0.7"
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ivoryos
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.7
|
|
4
4
|
Summary: an open-source Python package enabling Self-Driving Labs (SDLs) interoperability
|
|
5
5
|
Home-page: https://gitlab.com/heingroup/ivoryos
|
|
6
6
|
Author: Ivory Zhang
|
|
7
7
|
Author-email: ivoryzhang@chem.ubc.ca
|
|
8
8
|
License: MIT
|
|
9
|
-
Platform: UNKNOWN
|
|
10
9
|
Description-Content-Type: text/markdown
|
|
11
10
|
License-File: LICENSE
|
|
12
11
|
Requires-Dist: bcrypt
|
|
@@ -216,5 +215,3 @@ For an additional perspective related to the development of the tool, please see
|
|
|
216
215
|
Ivory Zhang, Lucy Hao
|
|
217
216
|
|
|
218
217
|
Authors acknowledge all former and current Hein Lab members for their valuable suggestions.
|
|
219
|
-
|
|
220
|
-
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
ivoryos/__init__.py,sha256=
|
|
2
|
-
ivoryos/config.py,sha256=
|
|
3
|
-
ivoryos/version.py,sha256=
|
|
1
|
+
ivoryos/__init__.py,sha256=9LWTha5Qprn0PX4pL0fbOS9VZ3p60Hd54APBxQPqv8A,8173
|
|
2
|
+
ivoryos/config.py,sha256=sk4dskm-K_Nv4uaA3QuE6xtew8wL6q3HmIoLgRm7p8U,2153
|
|
3
|
+
ivoryos/version.py,sha256=BW7SWRpHoxuOQZ67pS20yog2LWYl-nK7-BEFBNrHGgA,22
|
|
4
4
|
ivoryos/routes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
5
|
ivoryos/routes/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
6
|
ivoryos/routes/auth/auth.py,sha256=pvrYQN2XqH2OpZMxirFQZr9-c8yO29CpvQg9VR6JFsE,3256
|
|
@@ -45,8 +45,17 @@ ivoryos/utils/llm_agent.py,sha256=-lVCkjPlpLues9sNTmaT7bT4sdhWvV2DiojNwzB2Lcw,64
|
|
|
45
45
|
ivoryos/utils/script_runner.py,sha256=0b5hLKAF2o0SQKiArhUsG8-4MA-eniAcjwi8gCNVwtY,14542
|
|
46
46
|
ivoryos/utils/task_runner.py,sha256=u4nF0wOADu_HVlGYVTOXnUm1woWGgYAccr-ZCzgtb6Q,2899
|
|
47
47
|
ivoryos/utils/utils.py,sha256=nGSw3vHkxXedX4T-AFEWGzDIU7h0NjrAf0wBt0Og9pk,13886
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
49
|
+
tests/conftest.py,sha256=u2sQ6U-Lghyl7et1Oz6J2E5VZ47VINKcjRM_2leAE2s,3627
|
|
50
|
+
tests/integration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
51
|
+
tests/integration/test_route_auth.py,sha256=l3ZDqr0oiCWS3yYSXGK5yMP6qI2t7Sv5I9zoYTkiyQU,2754
|
|
52
|
+
tests/integration/test_route_control.py,sha256=YYIll84bTUEKiAxFiFSz6LF3fTldPNfCtHs0IR3mSdM,3935
|
|
53
|
+
tests/integration/test_route_database.py,sha256=mS026W_hEuCTMpSkdRWvM-f4MYykK_6nRDJ4K5a7QA0,2342
|
|
54
|
+
tests/integration/test_route_design.py,sha256=PJAvGRiCY6B53Pu1v5vPAVHHsuaqRmRKk2eesSNshLU,1157
|
|
55
|
+
tests/integration/test_route_main.py,sha256=bmuf8Y_9CRWhiLLf4up11ltEd5YCdsLx6I-o26VGDEw,1228
|
|
56
|
+
tests/integration/test_sockets.py,sha256=4ZyFyExm7a-DYzVqpzEONpWeb1a0IT68wyFaQu0rY_Y,925
|
|
57
|
+
ivoryos-1.0.7.dist-info/LICENSE,sha256=p2c8S8i-8YqMpZCJnadLz1-ofxnRMILzz6NCMIypRag,1084
|
|
58
|
+
ivoryos-1.0.7.dist-info/METADATA,sha256=zgcTPGg5Wu0wJfHlzHD38V7ZHqia5hEgQoK9lfvaAXw,8886
|
|
59
|
+
ivoryos-1.0.7.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
|
60
|
+
ivoryos-1.0.7.dist-info/top_level.txt,sha256=mIOiZkdpSwxFJt1R5fsyOff8mNprXHq1nMGNKNULIyE,14
|
|
61
|
+
ivoryos-1.0.7.dist-info/RECORD,,
|
tests/__init__.py
ADDED
|
File without changes
|
tests/conftest.py
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
import bcrypt
|
|
4
|
+
import pytest
|
|
5
|
+
from ivoryos.config import get_config
|
|
6
|
+
|
|
7
|
+
from ivoryos import create_app, socketio, db as _db, utils, global_config
|
|
8
|
+
from ivoryos.utils.db_models import User
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.fixture(scope='session')
|
|
12
|
+
def app():
|
|
13
|
+
"""Create a new app instance for the test session."""
|
|
14
|
+
_app = create_app(get_config('testing'))
|
|
15
|
+
return _app
|
|
16
|
+
|
|
17
|
+
@pytest.fixture
|
|
18
|
+
def client(app):
|
|
19
|
+
"""A test client for the app."""
|
|
20
|
+
with app.test_client() as client:
|
|
21
|
+
with app.app_context():
|
|
22
|
+
_db.create_all()
|
|
23
|
+
yield client
|
|
24
|
+
with app.app_context():
|
|
25
|
+
_db.drop_all()
|
|
26
|
+
|
|
27
|
+
# @pytest.fixture(scope='session')
|
|
28
|
+
# def db(app):
|
|
29
|
+
# """Session-wide test database."""
|
|
30
|
+
# with app.app_context():
|
|
31
|
+
# _db.create_all()
|
|
32
|
+
# yield _db
|
|
33
|
+
# _db.drop_all()
|
|
34
|
+
|
|
35
|
+
@pytest.fixture(scope='module')
|
|
36
|
+
def init_database(app):
|
|
37
|
+
"""
|
|
38
|
+
Creates the database tables and seeds it with a default test user.
|
|
39
|
+
This runs once per test module.
|
|
40
|
+
"""
|
|
41
|
+
with app.app_context():
|
|
42
|
+
# Drop everything first to ensure a clean slate
|
|
43
|
+
_db.drop_all()
|
|
44
|
+
# Create the database tables
|
|
45
|
+
_db.create_all()
|
|
46
|
+
|
|
47
|
+
# Insert a default user for authentication tests
|
|
48
|
+
# Note: In a real app with password hashing, you'd call a hash function here.
|
|
49
|
+
password = 'password'
|
|
50
|
+
bcrypt_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())
|
|
51
|
+
default_user = User(username='testuser', password=bcrypt_password)
|
|
52
|
+
_db.session.add(default_user)
|
|
53
|
+
_db.session.commit()
|
|
54
|
+
|
|
55
|
+
yield _db # this is where the testing happens!
|
|
56
|
+
|
|
57
|
+
# Teardown: drop all tables after the tests in the module are done
|
|
58
|
+
_db.drop_all()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# ---------------------
|
|
62
|
+
# Authentication Fixture
|
|
63
|
+
# ---------------------
|
|
64
|
+
|
|
65
|
+
@pytest.fixture(scope='function')
|
|
66
|
+
def auth(client, init_database):
|
|
67
|
+
"""
|
|
68
|
+
Logs in the default user for a single test function.
|
|
69
|
+
Depends on `init_database` to ensure the user exists.
|
|
70
|
+
Handles logout as part of teardown.
|
|
71
|
+
"""
|
|
72
|
+
# Log in the default user
|
|
73
|
+
client.post('/ivoryos/auth/login', data={
|
|
74
|
+
'username': 'testuser',
|
|
75
|
+
'password': 'password'
|
|
76
|
+
}, follow_redirects=True)
|
|
77
|
+
|
|
78
|
+
yield client # this is where the testing happens!
|
|
79
|
+
|
|
80
|
+
# Log out the user after the test is done
|
|
81
|
+
client.get('/ivoryos/auth/logout', follow_redirects=True)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@pytest.fixture
|
|
85
|
+
def socketio_client(app):
|
|
86
|
+
"""A test client for Socket.IO."""
|
|
87
|
+
return socketio.test_client(app)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class TestEnum(Enum):
|
|
91
|
+
"""An example Enum for testing type conversion."""
|
|
92
|
+
OPTION_A = 'A'
|
|
93
|
+
OPTION_B = 'B'
|
|
94
|
+
|
|
95
|
+
class DummyModule:
|
|
96
|
+
"""A more comprehensive dummy instrument for testing."""
|
|
97
|
+
def int_method(self, arg: int = 1):
|
|
98
|
+
return arg
|
|
99
|
+
|
|
100
|
+
def float_method(self, arg: float = 1.0):
|
|
101
|
+
return arg
|
|
102
|
+
|
|
103
|
+
def bool_method(self, arg: bool = False):
|
|
104
|
+
return arg
|
|
105
|
+
|
|
106
|
+
def list_method(self, arg: list = None):
|
|
107
|
+
return arg or []
|
|
108
|
+
|
|
109
|
+
def enum_method(self, arg: TestEnum = TestEnum.OPTION_A):
|
|
110
|
+
return arg
|
|
111
|
+
|
|
112
|
+
def str_method(self) -> dict:
|
|
113
|
+
return {'status': 'OK'}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@pytest.fixture
|
|
117
|
+
def test_deck(app):
|
|
118
|
+
"""
|
|
119
|
+
A fixture that creates and loads a predictable 'deck' of dummy instruments
|
|
120
|
+
for testing the dynamic control routes.
|
|
121
|
+
"""
|
|
122
|
+
dummy_module = DummyModule()
|
|
123
|
+
snapshot = utils.create_deck_snapshot(dummy_module)
|
|
124
|
+
|
|
125
|
+
with app.app_context():
|
|
126
|
+
global_config.deck_snapshot = snapshot
|
|
127
|
+
global_config.deck = dummy_module # instantiate the class
|
|
128
|
+
|
|
129
|
+
yield DummyModule
|
|
130
|
+
|
|
131
|
+
with app.app_context():
|
|
132
|
+
global_config.deck_snapshot = {}
|
|
133
|
+
global_config.deck = {}
|
|
File without changes
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from ivoryos.utils.db_models import User, db
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_get_signup(client):
|
|
5
|
+
"""
|
|
6
|
+
GIVEN a client
|
|
7
|
+
WHEN a GET request is made to /ivoryos/auth/signup
|
|
8
|
+
THEN check that signup page loads with 200 status and contains "Signup" text
|
|
9
|
+
"""
|
|
10
|
+
response = client.get("/ivoryos/auth/signup", follow_redirects=True)
|
|
11
|
+
assert response.status_code == 200
|
|
12
|
+
assert b"Signup" in response.data
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_route_auth_signup(client):
|
|
16
|
+
"""
|
|
17
|
+
GIVEN a client
|
|
18
|
+
WHEN a POST request is made to /ivoryos/auth/signup with valid credentials
|
|
19
|
+
THEN check that signup succeeds with 200 status and the user is created in database
|
|
20
|
+
"""
|
|
21
|
+
response = client.post("/ivoryos/auth/signup",
|
|
22
|
+
data={
|
|
23
|
+
"username": "second_testuser",
|
|
24
|
+
"password": "password"
|
|
25
|
+
},
|
|
26
|
+
follow_redirects=True
|
|
27
|
+
)
|
|
28
|
+
assert response.status_code == 200
|
|
29
|
+
assert b"Login" in response.data
|
|
30
|
+
|
|
31
|
+
# Verify user was created
|
|
32
|
+
with client.application.app_context():
|
|
33
|
+
user = db.session.query(User).filter(User.username == 'second_testuser').first()
|
|
34
|
+
assert user is not None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_duplicate_user_signup(client, init_database):
|
|
38
|
+
"""
|
|
39
|
+
GIVEN a client and init_database fixture
|
|
40
|
+
WHEN a POST request is made to signup with an existing username
|
|
41
|
+
THEN check that signup fails with 409 status and appropriate error message
|
|
42
|
+
"""
|
|
43
|
+
client.post('/ivoryos/auth/signup', data={
|
|
44
|
+
'username': 'existinguser',
|
|
45
|
+
'password': 'anotherpass'
|
|
46
|
+
})
|
|
47
|
+
# Try to create duplicate
|
|
48
|
+
response = client.post('/ivoryos/auth/signup', data={
|
|
49
|
+
'username': 'existinguser',
|
|
50
|
+
'password': 'anotherpass'
|
|
51
|
+
})
|
|
52
|
+
assert response.status_code == 409
|
|
53
|
+
assert b"Signup" in response.data
|
|
54
|
+
assert b"User already exists" in response.data
|
|
55
|
+
|
|
56
|
+
# Verify user was created
|
|
57
|
+
users = db.session.query(User).filter(User.username == 'existinguser').all()
|
|
58
|
+
assert len(users) == 1
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_failed_login(client):
|
|
62
|
+
"""
|
|
63
|
+
GIVEN a client and invalid login credentials
|
|
64
|
+
WHEN a POST request is made to /ivoryos/auth/login
|
|
65
|
+
THEN check that login fails with 401 status and the appropriate error message
|
|
66
|
+
"""
|
|
67
|
+
response = client.post('/ivoryos/auth/login', data={
|
|
68
|
+
'username': 'nonexistent',
|
|
69
|
+
'password': 'wrongpass'
|
|
70
|
+
})
|
|
71
|
+
assert response.status_code == 401
|
|
72
|
+
|
|
73
|
+
def test_logout(auth):
|
|
74
|
+
"""
|
|
75
|
+
GIVEN an authenticated client
|
|
76
|
+
WHEN a GET request is made to /ivoryos/auth/logout
|
|
77
|
+
THEN check that logout succeeds with 302 status and redirects to login
|
|
78
|
+
"""
|
|
79
|
+
response = auth.get('/ivoryos/auth/logout')
|
|
80
|
+
assert response.status_code == 302 # Redirect to login
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from unittest.mock import patch, Mock
|
|
2
|
+
|
|
3
|
+
from ivoryos.utils.db_models import Script
|
|
4
|
+
from ivoryos import db
|
|
5
|
+
|
|
6
|
+
def test_control_panel_redirects_anonymous(client):
|
|
7
|
+
"""
|
|
8
|
+
GIVEN an anonymous user
|
|
9
|
+
WHEN the control panel is accessed
|
|
10
|
+
THEN they should be redirected to the login page
|
|
11
|
+
"""
|
|
12
|
+
response = client.get('/ivoryos/control/home/deck', follow_redirects=True)
|
|
13
|
+
assert response.status_code == 200
|
|
14
|
+
assert b'Login' in response.data
|
|
15
|
+
|
|
16
|
+
def test_deck_control_for_auth_user(auth):
|
|
17
|
+
"""
|
|
18
|
+
GIVEN an authenticated user
|
|
19
|
+
WHEN the control panel is accessed
|
|
20
|
+
THEN the page should load successfully
|
|
21
|
+
"""
|
|
22
|
+
response = auth.get('/ivoryos/control/home/deck', follow_redirects=True)
|
|
23
|
+
assert response.status_code == 200
|
|
24
|
+
assert b'<title>IvoryOS | Devices</title>' in response.data # Assuming this text exists on the page
|
|
25
|
+
|
|
26
|
+
def test_temp_control_for_auth_user(auth):
|
|
27
|
+
"""
|
|
28
|
+
GIVEN an authenticated user
|
|
29
|
+
WHEN the control panel is accessed
|
|
30
|
+
THEN the page should load successfully
|
|
31
|
+
"""
|
|
32
|
+
response = auth.get('/ivoryos/control/home/temp', follow_redirects=True)
|
|
33
|
+
assert response.status_code == 200
|
|
34
|
+
# assert b'<title>IvoryOS | Devices</title>' in response.data # Assuming this text exists on the page
|
|
35
|
+
|
|
36
|
+
def test_new_controller_page(auth):
|
|
37
|
+
"""Test new controller page loads"""
|
|
38
|
+
response = auth.get('/ivoryos/control/new/')
|
|
39
|
+
assert response.status_code == 200
|
|
40
|
+
|
|
41
|
+
def test_download_proxy(self, auth_headers):
|
|
42
|
+
"""Test proxy download functionality"""
|
|
43
|
+
with patch('ivoryos.routes.control.control.global_config') as mock_config:
|
|
44
|
+
mock_config.deck_snapshot = {'test_instrument': {'test_method': {'signature': 'test()'}}}
|
|
45
|
+
response = auth_headers.get('/ivoryos/control/download')
|
|
46
|
+
assert response.status_code == 200
|
|
47
|
+
assert response.headers['Content-Disposition'].startswith('attachment')
|
|
48
|
+
|
|
49
|
+
def test_backend_control_get(self, auth_headers):
|
|
50
|
+
"""Test backend control GET endpoint"""
|
|
51
|
+
with patch('ivoryos.routes.control.control.global_config') as mock_config:
|
|
52
|
+
mock_config.deck_snapshot = {'test_instrument': {'test_method': {'signature': 'test()'}}}
|
|
53
|
+
response = auth_headers.get('/ivoryos/api/control/')
|
|
54
|
+
assert response.status_code == 200
|
|
55
|
+
assert response.is_json
|
|
56
|
+
|
|
57
|
+
@patch('ivoryos.routes.control.control.runner')
|
|
58
|
+
@patch('ivoryos.routes.control.control.find_instrument_by_name')
|
|
59
|
+
@patch('ivoryos.routes.control.control.create_form_from_module')
|
|
60
|
+
def test_backend_control_post(self, mock_form, mock_find, mock_runner, auth_headers):
|
|
61
|
+
"""Test backend control POST endpoint"""
|
|
62
|
+
# Setup mocks
|
|
63
|
+
mock_instrument = Mock()
|
|
64
|
+
mock_find.return_value = mock_instrument
|
|
65
|
+
mock_field = Mock()
|
|
66
|
+
mock_field.name = 'test_param'
|
|
67
|
+
mock_field.data = 'test_value'
|
|
68
|
+
mock_form_instance = Mock()
|
|
69
|
+
mock_form_instance.__iter__ = Mock(return_value=iter([mock_field]))
|
|
70
|
+
mock_form.return_value = {'test_method': mock_form_instance}
|
|
71
|
+
mock_runner.run_single_step.return_value = 'success'
|
|
72
|
+
response = auth_headers.post('/ivoryos/api/control/test_instrument', data={
|
|
73
|
+
'hidden_name': 'test_method',
|
|
74
|
+
'hidden_wait': 'true'
|
|
75
|
+
})
|
|
76
|
+
assert response.status_code == 200
|
|
77
|
+
|
|
78
|
+
# def test_control(auth, app):
|
|
79
|
+
# """
|
|
80
|
+
# GIVEN an authenticated user and an existing script
|
|
81
|
+
# WHEN a POST request is made to run the script
|
|
82
|
+
# THEN the user should be redirected and a success message shown
|
|
83
|
+
# """
|
|
84
|
+
# # We need to create a script in the database first
|
|
85
|
+
# with app.app_context():
|
|
86
|
+
# script = Script(name='My Test Script', author='testuser', content='print("hello")')
|
|
87
|
+
# db.session.add(script)
|
|
88
|
+
# db.session.commit()
|
|
89
|
+
# script_id = script.id
|
|
90
|
+
#
|
|
91
|
+
# # Simulate running the script
|
|
92
|
+
# response = auth.post(f'/ivoryos/control/run/{script_id}', follow_redirects=True)
|
|
93
|
+
# assert response.status_code == 200
|
|
94
|
+
# assert b'has been initiated' in response.data # Check for a flash message
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
3
|
+
from ivoryos.utils.db_models import Script, WorkflowRun, WorkflowStep
|
|
4
|
+
from ivoryos import db
|
|
5
|
+
|
|
6
|
+
def test_database_scripts_page(auth):
|
|
7
|
+
"""
|
|
8
|
+
GIVEN an authenticated user
|
|
9
|
+
WHEN they access the script database page
|
|
10
|
+
THEN the page should load and show their scripts
|
|
11
|
+
"""
|
|
12
|
+
# First, create a script so the page has something to render
|
|
13
|
+
with auth.application.app_context():
|
|
14
|
+
script = Script(name='test_script', author='testuser')
|
|
15
|
+
db.session.add(script)
|
|
16
|
+
db.session.commit()
|
|
17
|
+
|
|
18
|
+
response = auth.get('/ivoryos/database/scripts/', follow_redirects=True)
|
|
19
|
+
assert response.status_code == 200
|
|
20
|
+
# assert b'Scripts Database' in response.data
|
|
21
|
+
assert b'<title>IvoryOS | Design Database</title>' in response.data
|
|
22
|
+
|
|
23
|
+
def test_database_workflows_page(auth):
|
|
24
|
+
"""
|
|
25
|
+
GIVEN an authenticated user
|
|
26
|
+
WHEN they access the workflow database page
|
|
27
|
+
THEN the page should load and show past workflow runs
|
|
28
|
+
"""
|
|
29
|
+
# Create a workflow run to display
|
|
30
|
+
with auth.application.app_context():
|
|
31
|
+
run = WorkflowRun(name="untitled", platform="deck",start_time=datetime.now())
|
|
32
|
+
db.session.add(run)
|
|
33
|
+
db.session.commit()
|
|
34
|
+
run_id = run.id
|
|
35
|
+
|
|
36
|
+
response = auth.get('/ivoryos/database/workflows/', follow_redirects=True)
|
|
37
|
+
assert response.status_code == 200
|
|
38
|
+
assert b'Workflow ID' in response.data
|
|
39
|
+
# assert b'run_id' in response.data
|
|
40
|
+
|
|
41
|
+
def test_view_specific_workflow(auth):
|
|
42
|
+
"""
|
|
43
|
+
GIVEN an authenticated user and an existing workflow run
|
|
44
|
+
WHEN they access the specific URL for that workflow
|
|
45
|
+
THEN the detailed view for that run should be displayed
|
|
46
|
+
"""
|
|
47
|
+
with auth.application.app_context():
|
|
48
|
+
run = WorkflowRun(name='test_workflow', platform='test_platform', start_time=datetime.now())
|
|
49
|
+
db.session.add(run)
|
|
50
|
+
db.session.commit()
|
|
51
|
+
run_id = run.id
|
|
52
|
+
|
|
53
|
+
step = WorkflowStep(method_name='test_step', workflow_id=run_id, phase="main", run_error=False, start_time=datetime.now())
|
|
54
|
+
db.session.add(step)
|
|
55
|
+
db.session.commit()
|
|
56
|
+
# run_id = run.id
|
|
57
|
+
|
|
58
|
+
response = auth.get(f'/ivoryos/database/workflows/{run_id}', follow_redirects=True)
|
|
59
|
+
assert response.status_code == 200
|
|
60
|
+
# assert b'test_step' in response.data # Check for a title on the view page
|
|
61
|
+
assert b'test_workflow' in response.data
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
def test_design_page_loads_for_auth_user(auth):
|
|
2
|
+
"""
|
|
3
|
+
GIVEN an authenticated user
|
|
4
|
+
WHEN the design page is accessed
|
|
5
|
+
THEN the page should load successfully
|
|
6
|
+
"""
|
|
7
|
+
response = auth.get('/ivoryos/design/script/', follow_redirects=True)
|
|
8
|
+
assert response.status_code == 200
|
|
9
|
+
assert b'<title>IvoryOS | Design</title>' in response.data # Assuming this text exists
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_clear_canvas(auth):
|
|
13
|
+
"""
|
|
14
|
+
Tests clearing the design canvas.
|
|
15
|
+
"""
|
|
16
|
+
response = auth.get('/ivoryos/design/clear', follow_redirects=True)
|
|
17
|
+
assert response.status_code == 200
|
|
18
|
+
# assert b'Operations' in response.data
|
|
19
|
+
|
|
20
|
+
# def test_add_action(auth, test_deck):
|
|
21
|
+
# """
|
|
22
|
+
# Tests adding an action to the design canvas.
|
|
23
|
+
# """
|
|
24
|
+
# response = auth.post('/ivoryos/design/script/deck.dummy/', data={
|
|
25
|
+
# 'hidden_name': 'int_method',
|
|
26
|
+
# 'arg': '10'
|
|
27
|
+
# }, follow_redirects=True)
|
|
28
|
+
# assert response.status_code == 200
|
|
29
|
+
|
|
30
|
+
def test_experiment_run_page(auth):
|
|
31
|
+
"""
|
|
32
|
+
Tests the experiment run page.
|
|
33
|
+
"""
|
|
34
|
+
response = auth.get('/ivoryos/design/campaign')
|
|
35
|
+
assert response.status_code == 200
|
|
36
|
+
assert b'Run Panel' in response.data
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from flask_login import current_user
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_home_page_authenticated(auth, app):
|
|
5
|
+
"""
|
|
6
|
+
GIVEN an authenticated user (using the 'auth' fixture)
|
|
7
|
+
WHEN the home page is accessed
|
|
8
|
+
THEN check that they see the main application page
|
|
9
|
+
"""
|
|
10
|
+
with auth.application.test_request_context('/ivoryos/'):
|
|
11
|
+
# Manually trigger the before_request functions that Flask-Login uses
|
|
12
|
+
app.preprocess_request()
|
|
13
|
+
|
|
14
|
+
# Assert that the `current_user` proxy is now populated and authenticated
|
|
15
|
+
assert current_user.is_authenticated
|
|
16
|
+
assert current_user.username == 'testuser'
|
|
17
|
+
|
|
18
|
+
def test_help_page(client):
|
|
19
|
+
"""
|
|
20
|
+
GIVEN an unauthenticated user
|
|
21
|
+
WHEN they access the help page
|
|
22
|
+
THEN check that the page loads successfully and contains documentation content
|
|
23
|
+
"""
|
|
24
|
+
response = client.get('/ivoryos/help')
|
|
25
|
+
assert response.status_code == 200
|
|
26
|
+
assert b'Documentations' in response.data
|
|
27
|
+
|
|
28
|
+
def test_prefix_redirect(auth):
|
|
29
|
+
"""
|
|
30
|
+
GIVEN an authenticated user (using the 'auth' fixture)
|
|
31
|
+
WHEN the home page is accessed without prefix
|
|
32
|
+
THEN check that they see the main application page
|
|
33
|
+
"""
|
|
34
|
+
response = auth.get('/', follow_redirects=True)
|
|
35
|
+
assert response.status_code == 200
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
def test_socket_connection(socketio_client):
|
|
2
|
+
"""
|
|
3
|
+
Test that a client can successfully connect to the Socket.IO server.
|
|
4
|
+
"""
|
|
5
|
+
assert socketio_client.is_connected()
|
|
6
|
+
socketio_client.disconnect()
|
|
7
|
+
assert not socketio_client.is_connected()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# def test_logger_socket_event(socketio_client):
|
|
11
|
+
# """
|
|
12
|
+
# Test the custom logging event handler.
|
|
13
|
+
# (This assumes you have a handler like `@socketio.on('start_log')`)
|
|
14
|
+
# """
|
|
15
|
+
# # Connect the client
|
|
16
|
+
# socketio_client.connect()
|
|
17
|
+
#
|
|
18
|
+
# # Emit an event from the client to the server
|
|
19
|
+
# socketio_client.emit('start_log', {'logger_name': 'my_test_logger'})
|
|
20
|
+
#
|
|
21
|
+
# # Check what the server sent back to the client
|
|
22
|
+
# received = socketio_client.get_received()
|
|
23
|
+
#
|
|
24
|
+
# assert len(received) > 0
|
|
25
|
+
# assert received[0]['name'] == 'log_message' # Check for the event name
|
|
26
|
+
# assert 'Logger my_test_logger started' in received[0]['args'][0]['data']
|
|
File without changes
|
|
File without changes
|