jac-client 0.2.6__py3-none-any.whl → 0.2.11__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.
- jac_client/examples/all-in-one/{src/button.jac → button.jac} +4 -3
- jac_client/examples/all-in-one/components/CategoryFilter.jac +47 -0
- jac_client/examples/all-in-one/components/Header.jac +17 -0
- jac_client/examples/all-in-one/components/ProfitOverview.jac +64 -0
- jac_client/examples/all-in-one/components/Summary.jac +76 -0
- jac_client/examples/all-in-one/components/TransactionForm.jac +188 -0
- jac_client/examples/all-in-one/components/TransactionItem.jac +62 -0
- jac_client/examples/all-in-one/components/TransactionList.jac +44 -0
- jac_client/examples/all-in-one/components/button.jac +8 -0
- jac_client/examples/all-in-one/components/navigation.jac +126 -0
- jac_client/examples/all-in-one/constants/categories.jac +36 -0
- jac_client/examples/all-in-one/constants/clients.jac +12 -0
- jac_client/examples/all-in-one/context/BudgetContext.jac +31 -0
- jac_client/examples/all-in-one/hooks/useBudget.jac +122 -0
- jac_client/examples/all-in-one/hooks/useLocalStorage.jac +37 -0
- jac_client/examples/all-in-one/main.jac +542 -0
- jac_client/examples/all-in-one/pages/BudgetPlanner.jac +140 -0
- jac_client/examples/all-in-one/pages/FeaturesTest.jac +157 -0
- jac_client/examples/all-in-one/pages/LandingPage.jac +124 -0
- jac_client/examples/all-in-one/pages/budget_planner_ui.cl.jac +65 -0
- jac_client/examples/all-in-one/pages/features_test_ui.cl.jac +675 -0
- jac_client/examples/all-in-one/pages/loginPage.jac +127 -0
- jac_client/examples/all-in-one/pages/nestedDemo.jac +54 -0
- jac_client/examples/all-in-one/pages/notFound.jac +18 -0
- jac_client/examples/all-in-one/pages/signupPage.jac +127 -0
- jac_client/examples/all-in-one/utils/formatters.jac +49 -0
- jac_client/examples/asset-serving/css-with-image/main.jac +92 -0
- jac_client/examples/asset-serving/image-asset/main.jac +56 -0
- jac_client/examples/asset-serving/import-alias/main.jac +109 -0
- jac_client/examples/basic/main.jac +23 -0
- jac_client/examples/basic-auth/main.jac +363 -0
- jac_client/examples/basic-auth-with-router/main.jac +451 -0
- jac_client/examples/basic-full-stack/main.jac +362 -0
- jac_client/examples/css-styling/js-styling/main.jac +63 -0
- jac_client/examples/css-styling/material-ui/main.jac +122 -0
- jac_client/examples/css-styling/pure-css/main.jac +55 -0
- jac_client/examples/css-styling/sass-example/main.jac +55 -0
- jac_client/examples/css-styling/styled-components/main.jac +62 -0
- jac_client/examples/css-styling/tailwind-example/main.jac +74 -0
- jac_client/examples/full-stack-with-auth/main.jac +696 -0
- jac_client/examples/little-x/main.jac +681 -0
- jac_client/examples/little-x/src/submit-button.jac +15 -14
- jac_client/examples/nested-folders/nested-advance/main.jac +26 -0
- jac_client/examples/nested-folders/nested-advance/src/ButtonRoot.jac +4 -6
- jac_client/examples/nested-folders/nested-advance/src/level1/ButtonSecondL.jac +9 -13
- jac_client/examples/nested-folders/nested-advance/src/level1/Card.jac +29 -32
- jac_client/examples/nested-folders/nested-advance/src/level1/level2/ButtonThirdL.jac +12 -18
- jac_client/examples/nested-folders/nested-basic/{src/app.jac → main.jac} +7 -5
- jac_client/examples/nested-folders/nested-basic/src/button.jac +4 -3
- jac_client/examples/nested-folders/nested-basic/src/components/button.jac +4 -3
- jac_client/examples/ts-support/main.jac +35 -0
- jac_client/examples/with-router/main.jac +286 -0
- jac_client/plugin/cli.jac +507 -470
- jac_client/plugin/client.jac +30 -12
- jac_client/plugin/client_runtime.cl.jac +25 -15
- jac_client/plugin/impl/client.impl.jac +126 -26
- jac_client/plugin/impl/client_runtime.impl.jac +182 -10
- jac_client/plugin/plugin_config.jac +216 -34
- jac_client/plugin/src/__init__.jac +0 -2
- jac_client/plugin/src/compiler.jac +2 -2
- jac_client/plugin/src/config_loader.jac +1 -0
- jac_client/plugin/src/desktop_config.jac +31 -0
- jac_client/plugin/src/impl/compiler.impl.jac +99 -30
- jac_client/plugin/src/impl/config_loader.impl.jac +8 -0
- jac_client/plugin/src/impl/desktop_config.impl.jac +191 -0
- jac_client/plugin/src/impl/jac_to_js.impl.jac +5 -1
- jac_client/plugin/src/impl/package_installer.impl.jac +20 -20
- jac_client/plugin/src/impl/vite_bundler.impl.jac +384 -144
- jac_client/plugin/src/package_installer.jac +1 -1
- jac_client/plugin/src/targets/desktop/sidecar/main.py +144 -0
- jac_client/plugin/src/targets/desktop_target.jac +37 -0
- jac_client/plugin/src/targets/impl/desktop_target.impl.jac +2347 -0
- jac_client/plugin/src/targets/impl/registry.impl.jac +64 -0
- jac_client/plugin/src/targets/impl/web_target.impl.jac +157 -0
- jac_client/plugin/src/targets/register.jac +21 -0
- jac_client/plugin/src/targets/registry.jac +87 -0
- jac_client/plugin/src/targets/web_target.jac +35 -0
- jac_client/plugin/src/vite_bundler.jac +15 -1
- jac_client/plugin/utils/__init__.jac +3 -0
- jac_client/plugin/utils/bun_installer.jac +16 -0
- jac_client/plugin/utils/impl/bun_installer.impl.jac +99 -0
- jac_client/templates/client.jacpack +72 -0
- jac_client/templates/fullstack.jacpack +61 -0
- jac_client/tests/conftest.py +110 -52
- jac_client/tests/fixtures/spawn_test/app.jac +64 -70
- jac_client/tests/fixtures/with-ts/app.jac +28 -28
- jac_client/tests/test_cli.py +280 -113
- jac_client/tests/test_e2e.py +232 -0
- jac_client/tests/test_helpers.py +58 -0
- jac_client/tests/test_it.py +325 -154
- jac_client/tests/test_it_desktop.py +891 -0
- {jac_client-0.2.6.dist-info → jac_client-0.2.11.dist-info}/METADATA +20 -11
- jac_client-0.2.11.dist-info/RECORD +113 -0
- {jac_client-0.2.6.dist-info → jac_client-0.2.11.dist-info}/WHEEL +1 -1
- jac_client/examples/all-in-one/src/app.jac +0 -841
- jac_client/examples/all-in-one/src/components/button.jac +0 -7
- jac_client/examples/asset-serving/css-with-image/src/app.jac +0 -88
- jac_client/examples/asset-serving/image-asset/src/app.jac +0 -55
- jac_client/examples/asset-serving/import-alias/src/app.jac +0 -111
- jac_client/examples/basic/src/app.jac +0 -21
- jac_client/examples/basic-auth/src/app.jac +0 -377
- jac_client/examples/basic-auth-with-router/src/app.jac +0 -464
- jac_client/examples/basic-full-stack/src/app.jac +0 -365
- jac_client/examples/css-styling/js-styling/src/app.jac +0 -84
- jac_client/examples/css-styling/material-ui/src/app.jac +0 -122
- jac_client/examples/css-styling/pure-css/src/app.jac +0 -64
- jac_client/examples/css-styling/sass-example/src/app.jac +0 -64
- jac_client/examples/css-styling/styled-components/src/app.jac +0 -71
- jac_client/examples/css-styling/tailwind-example/src/app.jac +0 -63
- jac_client/examples/full-stack-with-auth/src/app.jac +0 -722
- jac_client/examples/little-x/src/app.jac +0 -719
- jac_client/examples/nested-folders/nested-advance/src/app.jac +0 -35
- jac_client/examples/ts-support/src/app.jac +0 -35
- jac_client/examples/with-router/src/app.jac +0 -323
- jac_client/plugin/src/babel_processor.jac +0 -18
- jac_client/plugin/src/impl/babel_processor.impl.jac +0 -84
- jac_client-0.2.6.dist-info/RECORD +0 -74
- {jac_client-0.2.6.dist-info → jac_client-0.2.11.dist-info}/entry_points.txt +0 -0
- {jac_client-0.2.6.dist-info → jac_client-0.2.11.dist-info}/top_level.txt +0 -0
jac_client/tests/test_it.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""End-to-end tests for `jac
|
|
1
|
+
"""End-to-end tests for `jac start` HTTP endpoints."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -6,7 +6,6 @@ import gc
|
|
|
6
6
|
import json
|
|
7
7
|
import os
|
|
8
8
|
import shutil
|
|
9
|
-
import socket
|
|
10
9
|
import tempfile
|
|
11
10
|
import time
|
|
12
11
|
from http.client import RemoteDisconnected
|
|
@@ -16,44 +15,12 @@ from urllib.request import Request, urlopen
|
|
|
16
15
|
|
|
17
16
|
import pytest
|
|
18
17
|
|
|
19
|
-
from
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
Jac.reset_machine()
|
|
26
|
-
yield
|
|
27
|
-
Jac.reset_machine()
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def _wait_for_port(
|
|
31
|
-
host: str,
|
|
32
|
-
port: int,
|
|
33
|
-
timeout: float = 60.0,
|
|
34
|
-
poll_interval: float = 0.5,
|
|
35
|
-
) -> None:
|
|
36
|
-
"""Block until a TCP port is accepting connections or timeout.
|
|
37
|
-
|
|
38
|
-
Raises:
|
|
39
|
-
TimeoutError: if the port is not accepting connections within timeout.
|
|
40
|
-
"""
|
|
41
|
-
deadline = time.time() + timeout
|
|
42
|
-
last_err: Exception | None = None
|
|
43
|
-
|
|
44
|
-
while time.time() < deadline:
|
|
45
|
-
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
46
|
-
sock.settimeout(poll_interval)
|
|
47
|
-
try:
|
|
48
|
-
sock.connect((host, port))
|
|
49
|
-
return
|
|
50
|
-
except OSError as exc: # Connection refused / timeout
|
|
51
|
-
last_err = exc
|
|
52
|
-
time.sleep(poll_interval)
|
|
53
|
-
|
|
54
|
-
raise TimeoutError(
|
|
55
|
-
f"Timed out waiting for {host}:{port} to become available. Last error: {last_err}"
|
|
56
|
-
)
|
|
18
|
+
from .test_helpers import (
|
|
19
|
+
get_env_with_npm, # Backward compat alias
|
|
20
|
+
get_free_port,
|
|
21
|
+
get_jac_command,
|
|
22
|
+
wait_for_port,
|
|
23
|
+
)
|
|
57
24
|
|
|
58
25
|
|
|
59
26
|
def _wait_for_endpoint(
|
|
@@ -117,7 +84,7 @@ def _wait_for_endpoint(
|
|
|
117
84
|
def test_all_in_one_app_endpoints() -> None:
|
|
118
85
|
"""Create a Jac app, copy @all-in-one into it, install packages from jac.toml, then verify endpoints."""
|
|
119
86
|
print(
|
|
120
|
-
"[DEBUG] Starting test_all_in_one_app_endpoints using jac create --
|
|
87
|
+
"[DEBUG] Starting test_all_in_one_app_endpoints using jac create --use client + @all-in-one"
|
|
121
88
|
)
|
|
122
89
|
|
|
123
90
|
# Resolve the path to jac_client/examples/all-in-one relative to this test file.
|
|
@@ -138,9 +105,9 @@ def test_all_in_one_app_endpoints() -> None:
|
|
|
138
105
|
print(f"[DEBUG] Changed working directory to {temp_dir}")
|
|
139
106
|
|
|
140
107
|
# 1. Create a new Jac app via CLI (requires jac + jac-client plugin installed)
|
|
141
|
-
print(f"[DEBUG] Running 'jac create --
|
|
108
|
+
print(f"[DEBUG] Running 'jac create --use client {app_name}'")
|
|
142
109
|
process = Popen(
|
|
143
|
-
["jac", "create", "--
|
|
110
|
+
["jac", "create", "--use", "client", app_name],
|
|
144
111
|
stdin=PIPE,
|
|
145
112
|
stdout=PIPE,
|
|
146
113
|
stderr=PIPE,
|
|
@@ -150,21 +117,21 @@ def test_all_in_one_app_endpoints() -> None:
|
|
|
150
117
|
returncode = process.returncode
|
|
151
118
|
|
|
152
119
|
print(
|
|
153
|
-
"[DEBUG] 'jac create --
|
|
120
|
+
"[DEBUG] 'jac create --use client' completed "
|
|
154
121
|
f"returncode={returncode}\n"
|
|
155
122
|
f"STDOUT:\n{stdout}\n"
|
|
156
123
|
f"STDERR:\n{stderr}\n"
|
|
157
124
|
)
|
|
158
125
|
|
|
159
|
-
# If the currently installed `jac` CLI does not support `create --
|
|
126
|
+
# If the currently installed `jac` CLI does not support `create --use client`,
|
|
160
127
|
# fail the test instead of skipping it.
|
|
161
|
-
if returncode != 0 and "unrecognized arguments: --
|
|
128
|
+
if returncode != 0 and "unrecognized arguments: --use" in stderr:
|
|
162
129
|
pytest.fail(
|
|
163
|
-
"Test failed: installed `jac` CLI does not support `create --
|
|
130
|
+
"Test failed: installed `jac` CLI does not support `create --use client`."
|
|
164
131
|
)
|
|
165
132
|
|
|
166
133
|
assert returncode == 0, (
|
|
167
|
-
f"jac create --
|
|
134
|
+
f"jac create --use client failed\nSTDOUT:\n{stdout}\nSTDERR:\n{stderr}\n"
|
|
168
135
|
)
|
|
169
136
|
|
|
170
137
|
project_path = os.path.join(temp_dir, app_name)
|
|
@@ -184,17 +151,17 @@ def test_all_in_one_app_endpoints() -> None:
|
|
|
184
151
|
else:
|
|
185
152
|
shutil.copy2(src, dst)
|
|
186
153
|
|
|
187
|
-
# 3. Install packages from jac.toml using `jac add --
|
|
154
|
+
# 3. Install packages from jac.toml using `jac add --npm`
|
|
188
155
|
# This reads packages from jac.toml, generates package.json, and runs npm install
|
|
189
|
-
print("[DEBUG] Running 'jac add --
|
|
156
|
+
print("[DEBUG] Running 'jac add --npm' to install packages from jac.toml")
|
|
190
157
|
jac_add_result = run(
|
|
191
|
-
["jac", "add", "--
|
|
158
|
+
["jac", "add", "--npm"],
|
|
192
159
|
cwd=project_path,
|
|
193
160
|
capture_output=True,
|
|
194
161
|
text=True,
|
|
195
162
|
)
|
|
196
163
|
print(
|
|
197
|
-
"[DEBUG] 'jac add --
|
|
164
|
+
"[DEBUG] 'jac add --npm' completed "
|
|
198
165
|
f"returncode={jac_add_result.returncode}\n"
|
|
199
166
|
f"STDOUT (truncated to 2000 chars):\n{jac_add_result.stdout[:2000]}\n"
|
|
200
167
|
f"STDERR (truncated to 2000 chars):\n{jac_add_result.stderr[:2000]}\n"
|
|
@@ -202,104 +169,97 @@ def test_all_in_one_app_endpoints() -> None:
|
|
|
202
169
|
|
|
203
170
|
if jac_add_result.returncode != 0:
|
|
204
171
|
pytest.fail(
|
|
205
|
-
f"Test failed: jac add --
|
|
172
|
+
f"Test failed: jac add --npm failed or npm is not available in PATH.\n"
|
|
206
173
|
f"STDOUT:\n{jac_add_result.stdout}\n"
|
|
207
174
|
f"STDERR:\n{jac_add_result.stderr}\n"
|
|
208
175
|
)
|
|
209
176
|
|
|
210
|
-
app_jac_path = os.path.join(project_path, "
|
|
211
|
-
assert os.path.isfile(app_jac_path), "all-in-one
|
|
177
|
+
app_jac_path = os.path.join(project_path, "main.jac")
|
|
178
|
+
assert os.path.isfile(app_jac_path), "all-in-one main.jac file missing"
|
|
212
179
|
|
|
213
|
-
# 4. Start the server: `jac
|
|
180
|
+
# 4. Start the server: `jac start main.jac`
|
|
214
181
|
# NOTE: We don't use text mode here, so `Popen` defaults to bytes.
|
|
215
182
|
# Use `Popen[bytes]` in the type annotation to keep mypy happy.
|
|
216
183
|
server: Popen[bytes] | None = None
|
|
184
|
+
# Use dynamic port allocation to avoid conflicts when running tests in parallel
|
|
185
|
+
server_port = get_free_port()
|
|
217
186
|
try:
|
|
218
|
-
print(
|
|
187
|
+
print(
|
|
188
|
+
f"[DEBUG] Starting server with 'jac start main.jac -p {server_port}'"
|
|
189
|
+
)
|
|
219
190
|
server = Popen(
|
|
220
|
-
["jac", "
|
|
191
|
+
["jac", "start", "main.jac", "-p", str(server_port)],
|
|
221
192
|
cwd=project_path,
|
|
222
193
|
)
|
|
223
|
-
|
|
224
194
|
# Wait for localhost:8000 to become available
|
|
225
|
-
print(
|
|
226
|
-
|
|
227
|
-
|
|
195
|
+
print(
|
|
196
|
+
f"[DEBUG] Waiting for server to be available on 127.0.0.1:{server_port}"
|
|
197
|
+
)
|
|
198
|
+
wait_for_port("127.0.0.1", server_port, timeout=90.0)
|
|
199
|
+
print(
|
|
200
|
+
f"[DEBUG] Server is now accepting connections on 127.0.0.1:{server_port}"
|
|
201
|
+
)
|
|
228
202
|
|
|
229
|
-
# "/" – server up
|
|
203
|
+
# "/" – server up (serves client app HTML due to base_route_app="app")
|
|
204
|
+
# Note: The root endpoint may return 503 while the client bundle is building.
|
|
205
|
+
# We use _wait_for_endpoint to retry on 503 until it's ready.
|
|
230
206
|
try:
|
|
231
|
-
print("[DEBUG] Sending GET request to root endpoint /")
|
|
232
|
-
|
|
233
|
-
"http://127.0.0.1:
|
|
234
|
-
timeout=
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
)
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
assert (
|
|
248
|
-
resp_root.headers.get("Cross-Origin-Opener-Policy")
|
|
249
|
-
== "same-origin"
|
|
250
|
-
), (
|
|
251
|
-
"Expected Cross-Origin-Opener-Policy header to be 'same-origin'"
|
|
252
|
-
)
|
|
253
|
-
assert (
|
|
254
|
-
resp_root.headers.get("Cross-Origin-Embedder-Policy")
|
|
255
|
-
== "require-corp"
|
|
256
|
-
), (
|
|
257
|
-
"Expected Cross-Origin-Embedder-Policy header to be 'require-corp'"
|
|
258
|
-
)
|
|
259
|
-
print(
|
|
260
|
-
"[DEBUG] Custom headers verified: COOP and COEP are present"
|
|
261
|
-
)
|
|
262
|
-
except (URLError, HTTPError) as exc:
|
|
207
|
+
print("[DEBUG] Sending GET request to root endpoint / (with retry)")
|
|
208
|
+
root_bytes = _wait_for_endpoint(
|
|
209
|
+
f"http://127.0.0.1:{server_port}",
|
|
210
|
+
timeout=120.0,
|
|
211
|
+
poll_interval=2.0,
|
|
212
|
+
request_timeout=30.0,
|
|
213
|
+
)
|
|
214
|
+
root_body = root_bytes.decode("utf-8", errors="ignore")
|
|
215
|
+
print(
|
|
216
|
+
"[DEBUG] Received response from root endpoint /\n"
|
|
217
|
+
f"Body (truncated to 500 chars):\n{root_body[:500]}"
|
|
218
|
+
)
|
|
219
|
+
# With base_route_app="app", root serves client HTML
|
|
220
|
+
assert "<!DOCTYPE html>" in root_body or "<html" in root_body
|
|
221
|
+
assert '<div id="root">' in root_body
|
|
222
|
+
except (URLError, HTTPError, TimeoutError) as exc:
|
|
263
223
|
print(f"[DEBUG] Error while requesting root endpoint: {exc}")
|
|
264
224
|
pytest.fail(f"Failed to GET root endpoint: {exc}")
|
|
265
225
|
|
|
266
|
-
# "/
|
|
226
|
+
# "/cl/app" – main page is loading
|
|
267
227
|
# Note: This endpoint may return 503 (temporary) while the page is being compiled,
|
|
268
228
|
# or 500 (permanent) if there's a compilation error. We use _wait_for_endpoint
|
|
269
229
|
# to retry on 503 until it's ready, but it will fail immediately on 500.
|
|
270
230
|
try:
|
|
271
231
|
print(
|
|
272
|
-
"[DEBUG] Sending GET request to /
|
|
232
|
+
"[DEBUG] Sending GET request to /cl/app endpoint (with retry)"
|
|
273
233
|
)
|
|
274
234
|
page_bytes = _wait_for_endpoint(
|
|
275
|
-
"http://127.0.0.1:
|
|
235
|
+
f"http://127.0.0.1:{server_port}/cl/app",
|
|
276
236
|
timeout=120.0,
|
|
277
237
|
poll_interval=2.0,
|
|
278
238
|
request_timeout=30.0,
|
|
279
239
|
)
|
|
280
240
|
page_body = page_bytes.decode("utf-8", errors="ignore")
|
|
281
241
|
print(
|
|
282
|
-
"[DEBUG] Received response from /
|
|
242
|
+
"[DEBUG] Received response from /cl/app endpoint\n"
|
|
283
243
|
f"Body (truncated to 500 chars):\n{page_body[:500]}"
|
|
284
244
|
)
|
|
285
245
|
assert "<html" in page_body.lower()
|
|
286
246
|
except (URLError, HTTPError, TimeoutError, RemoteDisconnected) as exc:
|
|
287
|
-
print(f"[DEBUG] Error while requesting /
|
|
288
|
-
pytest.fail(f"Failed to GET /
|
|
247
|
+
print(f"[DEBUG] Error while requesting /cl/app endpoint: {exc}")
|
|
248
|
+
pytest.fail(f"Failed to GET /cl/app endpoint: {exc}")
|
|
289
249
|
|
|
290
|
-
# "/
|
|
250
|
+
# "/cl/app#/nested" – relative paths / nested route
|
|
291
251
|
# (hash fragment is client-side only but server should still serve the app shell)
|
|
292
252
|
try:
|
|
293
|
-
print("[DEBUG] Sending GET request to /
|
|
253
|
+
print("[DEBUG] Sending GET request to /cl/app#/nested endpoint")
|
|
294
254
|
with urlopen(
|
|
295
|
-
"http://127.0.0.1:
|
|
255
|
+
f"http://127.0.0.1:{server_port}/cl/app#/nested",
|
|
296
256
|
timeout=200,
|
|
297
257
|
) as resp_nested:
|
|
298
258
|
nested_body = resp_nested.read().decode(
|
|
299
259
|
"utf-8", errors="ignore"
|
|
300
260
|
)
|
|
301
261
|
print(
|
|
302
|
-
"[DEBUG] Received response from /
|
|
262
|
+
"[DEBUG] Received response from /cl/app#/nested endpoint\n"
|
|
303
263
|
f"Status: {resp_nested.status}\n"
|
|
304
264
|
f"Body (truncated to 500 chars):\n{nested_body[:500]}"
|
|
305
265
|
)
|
|
@@ -307,37 +267,18 @@ def test_all_in_one_app_endpoints() -> None:
|
|
|
307
267
|
assert "<html" in nested_body.lower()
|
|
308
268
|
except (URLError, HTTPError) as exc:
|
|
309
269
|
print(
|
|
310
|
-
f"[DEBUG] Error while requesting /
|
|
270
|
+
f"[DEBUG] Error while requesting /cl/app#/nested endpoint: {exc}"
|
|
311
271
|
)
|
|
312
|
-
pytest.fail("Failed to GET /
|
|
272
|
+
pytest.fail("Failed to GET /cl/app#/nested endpoint")
|
|
313
273
|
|
|
314
|
-
#
|
|
315
|
-
#
|
|
316
|
-
try:
|
|
317
|
-
print(
|
|
318
|
-
"[DEBUG] Sending GET request to /static/main.css (with retry)"
|
|
319
|
-
)
|
|
320
|
-
css_bytes = _wait_for_endpoint(
|
|
321
|
-
"http://127.0.0.1:8000/static/main.css",
|
|
322
|
-
timeout=60.0,
|
|
323
|
-
poll_interval=2.0,
|
|
324
|
-
request_timeout=20.0,
|
|
325
|
-
)
|
|
326
|
-
css_body = css_bytes.decode("utf-8", errors="ignore")
|
|
327
|
-
print(
|
|
328
|
-
"[DEBUG] Received response from /static/main.css\n"
|
|
329
|
-
f"Body (truncated to 500 chars):\n{css_body[:500]}"
|
|
330
|
-
)
|
|
331
|
-
assert len(css_body.strip()) > 0, "CSS file should not be empty"
|
|
332
|
-
except (URLError, HTTPError, TimeoutError, RemoteDisconnected) as exc:
|
|
333
|
-
print(f"[DEBUG] Error while requesting /static/main.css: {exc}")
|
|
334
|
-
pytest.fail(f"Failed to GET /static/main.css after retries: {exc}")
|
|
274
|
+
# Note: CSS serving is tested separately in test_css_with_image
|
|
275
|
+
# The CSS is bundled into client.js so no separate /static/styles.css endpoint
|
|
335
276
|
|
|
336
277
|
# "/static/assets/burger.png" – static files are loading
|
|
337
278
|
try:
|
|
338
279
|
print("[DEBUG] Sending GET request to /static/assets/burger.png")
|
|
339
280
|
with urlopen(
|
|
340
|
-
"http://127.0.0.1:
|
|
281
|
+
f"http://127.0.0.1:{server_port}/static/assets/burger.png",
|
|
341
282
|
timeout=20,
|
|
342
283
|
) as resp_png:
|
|
343
284
|
png_bytes = resp_png.read()
|
|
@@ -363,7 +304,7 @@ def test_all_in_one_app_endpoints() -> None:
|
|
|
363
304
|
"[DEBUG] Sending GET request to /workers/worker.js (with retry)"
|
|
364
305
|
)
|
|
365
306
|
worker_js_bytes = _wait_for_endpoint(
|
|
366
|
-
"http://127.0.0.1:
|
|
307
|
+
f"http://127.0.0.1:{server_port}/workers/worker.js",
|
|
367
308
|
timeout=60.0,
|
|
368
309
|
poll_interval=2.0,
|
|
369
310
|
request_timeout=20.0,
|
|
@@ -385,13 +326,18 @@ def test_all_in_one_app_endpoints() -> None:
|
|
|
385
326
|
f"Failed to GET /workers/worker.js after retries: {exc}"
|
|
386
327
|
)
|
|
387
328
|
|
|
388
|
-
#
|
|
329
|
+
# POST /walker/get_server_message – walkers are integrated and up and running
|
|
389
330
|
try:
|
|
390
|
-
print(
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
331
|
+
print(
|
|
332
|
+
"[DEBUG] Sending POST request to /walker/get_server_message endpoint"
|
|
333
|
+
)
|
|
334
|
+
req = Request(
|
|
335
|
+
f"http://127.0.0.1:{server_port}/walker/get_server_message",
|
|
336
|
+
data=json.dumps({}).encode("utf-8"),
|
|
337
|
+
headers={"Content-Type": "application/json"},
|
|
338
|
+
method="POST",
|
|
339
|
+
)
|
|
340
|
+
with urlopen(req, timeout=20) as resp_walker:
|
|
395
341
|
walker_body = resp_walker.read().decode(
|
|
396
342
|
"utf-8", errors="ignore"
|
|
397
343
|
)
|
|
@@ -401,12 +347,13 @@ def test_all_in_one_app_endpoints() -> None:
|
|
|
401
347
|
f"Body (truncated to 500 chars):\n{walker_body[:500]}"
|
|
402
348
|
)
|
|
403
349
|
assert resp_walker.status == 200
|
|
404
|
-
|
|
405
|
-
|
|
350
|
+
# The walker reports "hello from a basic walker!"
|
|
351
|
+
assert "hello from a basic walker" in walker_body.lower()
|
|
352
|
+
except (URLError, HTTPError, RemoteDisconnected) as exc:
|
|
406
353
|
print(
|
|
407
354
|
f"[DEBUG] Error while requesting /walker/get_server_message: {exc}"
|
|
408
355
|
)
|
|
409
|
-
pytest.fail("Failed to
|
|
356
|
+
pytest.fail("Failed to POST /walker/get_server_message")
|
|
410
357
|
|
|
411
358
|
# POST /walker/create_todo – create a Todo via walker HTTP API
|
|
412
359
|
try:
|
|
@@ -417,7 +364,7 @@ def test_all_in_one_app_endpoints() -> None:
|
|
|
417
364
|
"text": "Sample todo from all-in-one app",
|
|
418
365
|
}
|
|
419
366
|
req = Request(
|
|
420
|
-
"http://127.0.0.1:
|
|
367
|
+
f"http://127.0.0.1:{server_port}/walker/create_todo",
|
|
421
368
|
data=json.dumps(payload).encode("utf-8"),
|
|
422
369
|
headers={"Content-Type": "application/json"},
|
|
423
370
|
method="POST",
|
|
@@ -434,7 +381,7 @@ def test_all_in_one_app_endpoints() -> None:
|
|
|
434
381
|
assert resp_create.status == 200
|
|
435
382
|
# Basic sanity check: created Todo text should appear in the response payload.
|
|
436
383
|
assert "Sample todo from all-in-one app" in create_body
|
|
437
|
-
except (URLError, HTTPError) as exc:
|
|
384
|
+
except (URLError, HTTPError, RemoteDisconnected) as exc:
|
|
438
385
|
print(f"[DEBUG] Error while requesting /walker/create_todo: {exc}")
|
|
439
386
|
pytest.fail("Failed to POST /walker/create_todo")
|
|
440
387
|
|
|
@@ -448,7 +395,7 @@ def test_all_in_one_app_endpoints() -> None:
|
|
|
448
395
|
"password": test_password,
|
|
449
396
|
}
|
|
450
397
|
req_register = Request(
|
|
451
|
-
"http://127.0.0.1:
|
|
398
|
+
f"http://127.0.0.1:{server_port}/user/register",
|
|
452
399
|
data=json.dumps(register_payload).encode("utf-8"),
|
|
453
400
|
headers={"Content-Type": "application/json"},
|
|
454
401
|
method="POST",
|
|
@@ -463,7 +410,9 @@ def test_all_in_one_app_endpoints() -> None:
|
|
|
463
410
|
f"Body (truncated to 500 chars):\n{register_body[:500]}"
|
|
464
411
|
)
|
|
465
412
|
assert resp_register.status == 201
|
|
466
|
-
|
|
413
|
+
register_response = json.loads(register_body)
|
|
414
|
+
# Handle new TransportResponse envelope format
|
|
415
|
+
register_data = register_response.get("data", register_response)
|
|
467
416
|
assert "username" in register_data
|
|
468
417
|
assert "token" in register_data
|
|
469
418
|
assert "root_id" in register_data
|
|
@@ -475,7 +424,7 @@ def test_all_in_one_app_endpoints() -> None:
|
|
|
475
424
|
f"Token: {register_data['token'][:20]}...\n"
|
|
476
425
|
f"Root ID: {register_data['root_id']}"
|
|
477
426
|
)
|
|
478
|
-
except (URLError, HTTPError) as exc:
|
|
427
|
+
except (URLError, HTTPError, RemoteDisconnected) as exc:
|
|
479
428
|
print(f"[DEBUG] Error while requesting /user/register: {exc}")
|
|
480
429
|
pytest.fail("Failed to POST /user/register")
|
|
481
430
|
|
|
@@ -487,7 +436,7 @@ def test_all_in_one_app_endpoints() -> None:
|
|
|
487
436
|
"password": test_password,
|
|
488
437
|
}
|
|
489
438
|
req_login = Request(
|
|
490
|
-
"http://127.0.0.1:
|
|
439
|
+
f"http://127.0.0.1:{server_port}/user/login",
|
|
491
440
|
data=json.dumps(login_payload).encode("utf-8"),
|
|
492
441
|
headers={"Content-Type": "application/json"},
|
|
493
442
|
method="POST",
|
|
@@ -500,14 +449,16 @@ def test_all_in_one_app_endpoints() -> None:
|
|
|
500
449
|
f"Body (truncated to 500 chars):\n{login_body[:500]}"
|
|
501
450
|
)
|
|
502
451
|
assert resp_login.status == 200
|
|
503
|
-
|
|
452
|
+
login_response = json.loads(login_body)
|
|
453
|
+
# Handle new TransportResponse envelope format
|
|
454
|
+
login_data = login_response.get("data", login_response)
|
|
504
455
|
assert "token" in login_data
|
|
505
456
|
assert len(login_data["token"]) > 0
|
|
506
457
|
print(
|
|
507
458
|
f"[DEBUG] Successfully logged in user: {test_username}\n"
|
|
508
459
|
f"Token: {login_data['token'][:20]}..."
|
|
509
460
|
)
|
|
510
|
-
except (URLError, HTTPError) as exc:
|
|
461
|
+
except (URLError, HTTPError, RemoteDisconnected) as exc:
|
|
511
462
|
print(f"[DEBUG] Error while requesting /user/login: {exc}")
|
|
512
463
|
pytest.fail("Failed to POST /user/login")
|
|
513
464
|
|
|
@@ -521,7 +472,7 @@ def test_all_in_one_app_endpoints() -> None:
|
|
|
521
472
|
"password": "wrong_password",
|
|
522
473
|
}
|
|
523
474
|
req_invalid_login = Request(
|
|
524
|
-
"http://127.0.0.1:
|
|
475
|
+
f"http://127.0.0.1:{server_port}/user/login",
|
|
525
476
|
data=json.dumps(invalid_login_payload).encode("utf-8"),
|
|
526
477
|
headers={"Content-Type": "application/json"},
|
|
527
478
|
method="POST",
|
|
@@ -552,14 +503,14 @@ def test_all_in_one_app_endpoints() -> None:
|
|
|
552
503
|
assert http_err.code in (400, 401, 403), (
|
|
553
504
|
f"Expected 400/401/403 for invalid login, got {http_err.code}"
|
|
554
505
|
)
|
|
555
|
-
except URLError as exc:
|
|
506
|
+
except (URLError, RemoteDisconnected) as exc:
|
|
556
507
|
print(
|
|
557
508
|
f"[DEBUG] Unexpected error while testing invalid login: {exc}"
|
|
558
509
|
)
|
|
559
510
|
pytest.fail("Unexpected error testing invalid login")
|
|
560
511
|
|
|
561
512
|
# Verify TypeScript component is working - check that page loads with TS component
|
|
562
|
-
# The /
|
|
513
|
+
# The /cl/app endpoint should serve the app which includes the TypeScript Card component
|
|
563
514
|
try:
|
|
564
515
|
print("[DEBUG] Verifying TypeScript component integration")
|
|
565
516
|
# The page should load successfully (already tested above)
|
|
@@ -570,12 +521,10 @@ def test_all_in_one_app_endpoints() -> None:
|
|
|
570
521
|
print(f"[DEBUG] Error verifying TypeScript component: {exc}")
|
|
571
522
|
pytest.fail("Failed to verify TypeScript component integration")
|
|
572
523
|
|
|
573
|
-
# Verify nested folder imports are working - /
|
|
524
|
+
# Verify nested folder imports are working - /cl/app#/nested route
|
|
574
525
|
# This route uses nested folder imports (components.button and button)
|
|
575
526
|
try:
|
|
576
|
-
print(
|
|
577
|
-
"[DEBUG] Verifying nested folder imports via /page/app#/nested"
|
|
578
|
-
)
|
|
527
|
+
print("[DEBUG] Verifying nested folder imports via /cl/app#/nested")
|
|
579
528
|
# The nested route should load successfully (already tested above)
|
|
580
529
|
# Nested imports are compiled and included in the bundle
|
|
581
530
|
assert "<html" in nested_body.lower(), (
|
|
@@ -607,3 +556,225 @@ def test_all_in_one_app_endpoints() -> None:
|
|
|
607
556
|
os.chdir(original_cwd)
|
|
608
557
|
# Final garbage collection to ensure all resources are released
|
|
609
558
|
gc.collect()
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
def test_default_client_app_renders() -> None:
|
|
562
|
+
"""Test that a default `jac create --use client` app renders correctly when served.
|
|
563
|
+
|
|
564
|
+
This test validates the out-of-the-box experience:
|
|
565
|
+
1. Creates a new client app using `jac create --use client`
|
|
566
|
+
2. Installs packages
|
|
567
|
+
3. Starts the server
|
|
568
|
+
4. Validates that the default app renders with expected content
|
|
569
|
+
"""
|
|
570
|
+
print("[DEBUG] Starting test_default_client_app_renders")
|
|
571
|
+
|
|
572
|
+
app_name = "e2e-default-app"
|
|
573
|
+
|
|
574
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
575
|
+
print(f"[DEBUG] Created temporary directory at {temp_dir}")
|
|
576
|
+
original_cwd = os.getcwd()
|
|
577
|
+
try:
|
|
578
|
+
os.chdir(temp_dir)
|
|
579
|
+
print(f"[DEBUG] Changed working directory to {temp_dir}")
|
|
580
|
+
|
|
581
|
+
# 1. Create a new default Jac client app
|
|
582
|
+
jac_cmd = get_jac_command()
|
|
583
|
+
env = get_env_with_npm()
|
|
584
|
+
print(
|
|
585
|
+
f"[DEBUG] Running '{' '.join(jac_cmd)} create --use client {app_name}'"
|
|
586
|
+
)
|
|
587
|
+
process = Popen(
|
|
588
|
+
[*jac_cmd, "create", "--use", "client", app_name],
|
|
589
|
+
stdin=PIPE,
|
|
590
|
+
stdout=PIPE,
|
|
591
|
+
stderr=PIPE,
|
|
592
|
+
text=True,
|
|
593
|
+
env=env,
|
|
594
|
+
)
|
|
595
|
+
stdout, stderr = process.communicate()
|
|
596
|
+
returncode = process.returncode
|
|
597
|
+
|
|
598
|
+
print(
|
|
599
|
+
f"[DEBUG] 'jac create --use client' completed returncode={returncode}\n"
|
|
600
|
+
f"STDOUT:\n{stdout}\n"
|
|
601
|
+
f"STDERR:\n{stderr}\n"
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
if returncode != 0 and "unrecognized arguments: --use" in stderr:
|
|
605
|
+
pytest.fail(
|
|
606
|
+
"Test failed: installed `jac` CLI does not support `create --use client`."
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
assert returncode == 0, (
|
|
610
|
+
f"jac create --use client failed\nSTDOUT:\n{stdout}\nSTDERR:\n{stderr}\n"
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
project_path = os.path.join(temp_dir, app_name)
|
|
614
|
+
print(f"[DEBUG] Created default Jac client app at {project_path}")
|
|
615
|
+
assert os.path.isdir(project_path)
|
|
616
|
+
|
|
617
|
+
# Verify expected files were created (new structure: main.jac at root)
|
|
618
|
+
main_jac_path = os.path.join(project_path, "main.jac")
|
|
619
|
+
assert os.path.isfile(main_jac_path), (
|
|
620
|
+
"main.jac should exist at project root"
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
# Components are now at root level (not src/components)
|
|
624
|
+
button_jac_path = os.path.join(project_path, "components", "Button.cl.jac")
|
|
625
|
+
assert os.path.isfile(button_jac_path), (
|
|
626
|
+
"components/Button.cl.jac should exist"
|
|
627
|
+
)
|
|
628
|
+
|
|
629
|
+
jac_toml_path = os.path.join(project_path, "jac.toml")
|
|
630
|
+
assert os.path.isfile(jac_toml_path), "jac.toml should exist"
|
|
631
|
+
|
|
632
|
+
# 2. Ensure packages are installed (jac create --use client should have done this)
|
|
633
|
+
# If node_modules doesn't exist, run jac add --npm
|
|
634
|
+
node_modules_path = os.path.join(
|
|
635
|
+
project_path, ".jac", "client", "node_modules"
|
|
636
|
+
)
|
|
637
|
+
if not os.path.isdir(node_modules_path):
|
|
638
|
+
print("[DEBUG] node_modules not found, running 'jac add --npm'")
|
|
639
|
+
jac_add_result = run(
|
|
640
|
+
[*jac_cmd, "add", "--npm"],
|
|
641
|
+
cwd=project_path,
|
|
642
|
+
capture_output=True,
|
|
643
|
+
text=True,
|
|
644
|
+
env=env,
|
|
645
|
+
)
|
|
646
|
+
print(
|
|
647
|
+
f"[DEBUG] 'jac add --npm' completed returncode={jac_add_result.returncode}\n"
|
|
648
|
+
f"STDOUT (truncated):\n{jac_add_result.stdout[:1000]}\n"
|
|
649
|
+
f"STDERR (truncated):\n{jac_add_result.stderr[:1000]}\n"
|
|
650
|
+
)
|
|
651
|
+
if jac_add_result.returncode != 0:
|
|
652
|
+
pytest.fail(
|
|
653
|
+
f"jac add --npm failed\n"
|
|
654
|
+
f"STDOUT:\n{jac_add_result.stdout}\n"
|
|
655
|
+
f"STDERR:\n{jac_add_result.stderr}\n"
|
|
656
|
+
)
|
|
657
|
+
|
|
658
|
+
# 3. Start the server (now uses main.jac at project root)
|
|
659
|
+
server: Popen[bytes] | None = None
|
|
660
|
+
# Use dynamic port allocation to avoid conflicts when running tests in parallel
|
|
661
|
+
server_port = get_free_port()
|
|
662
|
+
try:
|
|
663
|
+
print(
|
|
664
|
+
f"[DEBUG] Starting server with 'jac start main.jac -p {server_port}'"
|
|
665
|
+
)
|
|
666
|
+
server = Popen(
|
|
667
|
+
[*jac_cmd, "start", "main.jac", "-p", str(server_port)],
|
|
668
|
+
cwd=project_path,
|
|
669
|
+
env=env,
|
|
670
|
+
)
|
|
671
|
+
|
|
672
|
+
# Wait for server to be ready
|
|
673
|
+
print(f"[DEBUG] Waiting for server on 127.0.0.1:{server_port}")
|
|
674
|
+
wait_for_port("127.0.0.1", server_port, timeout=90.0)
|
|
675
|
+
print(
|
|
676
|
+
f"[DEBUG] Server is accepting connections on 127.0.0.1:{server_port}"
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
# 4. Test root endpoint - for client-only apps, root serves the HTML app
|
|
680
|
+
# Note: The root endpoint may return 503 while the client bundle is building.
|
|
681
|
+
# We use _wait_for_endpoint to retry on 503 until it's ready.
|
|
682
|
+
try:
|
|
683
|
+
print("[DEBUG] Testing root endpoint / (with retry)")
|
|
684
|
+
root_bytes = _wait_for_endpoint(
|
|
685
|
+
f"http://127.0.0.1:{server_port}",
|
|
686
|
+
timeout=120.0,
|
|
687
|
+
poll_interval=2.0,
|
|
688
|
+
request_timeout=30.0,
|
|
689
|
+
)
|
|
690
|
+
root_body = root_bytes.decode("utf-8", errors="ignore")
|
|
691
|
+
print(
|
|
692
|
+
f"[DEBUG] Root response:\nBody (truncated):\n{root_body[:500]}"
|
|
693
|
+
)
|
|
694
|
+
# For client-only apps, root returns HTML with the React app
|
|
695
|
+
assert "<html" in root_body.lower(), (
|
|
696
|
+
"Root should return HTML for client-only app"
|
|
697
|
+
)
|
|
698
|
+
assert "<script" in root_body.lower(), (
|
|
699
|
+
"Root should include script tag for client bundle"
|
|
700
|
+
)
|
|
701
|
+
except (URLError, HTTPError, TimeoutError) as exc:
|
|
702
|
+
print(f"[DEBUG] Error at root endpoint: {exc}")
|
|
703
|
+
pytest.fail(f"Failed to GET root endpoint: {exc}")
|
|
704
|
+
|
|
705
|
+
# 5. Test client app endpoint - the rendered React app
|
|
706
|
+
try:
|
|
707
|
+
print("[DEBUG] Testing client app endpoint /cl/app")
|
|
708
|
+
page_bytes = _wait_for_endpoint(
|
|
709
|
+
f"http://127.0.0.1:{server_port}/cl/app",
|
|
710
|
+
timeout=120.0,
|
|
711
|
+
poll_interval=2.0,
|
|
712
|
+
request_timeout=30.0,
|
|
713
|
+
)
|
|
714
|
+
page_body = page_bytes.decode("utf-8", errors="ignore")
|
|
715
|
+
print(
|
|
716
|
+
f"[DEBUG] Client app response:\n"
|
|
717
|
+
f"Body (truncated):\n{page_body[:1000]}"
|
|
718
|
+
)
|
|
719
|
+
|
|
720
|
+
# Validate HTML structure
|
|
721
|
+
assert "<html" in page_body.lower(), "Response should contain HTML"
|
|
722
|
+
assert "<body" in page_body.lower(), "Response should contain body"
|
|
723
|
+
|
|
724
|
+
# The page should include the bundled JavaScript
|
|
725
|
+
# that will render "Hello, World!" client-side
|
|
726
|
+
assert (
|
|
727
|
+
"<script" in page_body.lower() or "src=" in page_body.lower()
|
|
728
|
+
), "Response should include script tags for React app"
|
|
729
|
+
|
|
730
|
+
except (URLError, HTTPError, TimeoutError) as exc:
|
|
731
|
+
print(f"[DEBUG] Error at /cl/app endpoint: {exc}")
|
|
732
|
+
pytest.fail(f"Failed to GET /cl/app endpoint: {exc}")
|
|
733
|
+
|
|
734
|
+
# 6. Test that static JS bundle is being served
|
|
735
|
+
try:
|
|
736
|
+
print("[DEBUG] Testing that client.js bundle is served")
|
|
737
|
+
# Extract the client.js path from the HTML
|
|
738
|
+
import re
|
|
739
|
+
|
|
740
|
+
script_match = re.search(
|
|
741
|
+
r'src="(/static/client\.js[^"]*)"', root_body
|
|
742
|
+
)
|
|
743
|
+
if script_match:
|
|
744
|
+
js_path = script_match.group(1)
|
|
745
|
+
js_url = f"http://127.0.0.1:{server_port}{js_path}"
|
|
746
|
+
print(f"[DEBUG] Fetching JS bundle from {js_url}")
|
|
747
|
+
with urlopen(js_url, timeout=30) as resp:
|
|
748
|
+
js_body = resp.read().decode("utf-8", errors="ignore")
|
|
749
|
+
assert resp.status == 200, "JS bundle should return 200"
|
|
750
|
+
assert len(js_body) > 0, "JS bundle should not be empty"
|
|
751
|
+
print(
|
|
752
|
+
f"[DEBUG] JS bundle fetched successfully "
|
|
753
|
+
f"({len(js_body)} bytes)"
|
|
754
|
+
)
|
|
755
|
+
else:
|
|
756
|
+
print("[DEBUG] Warning: Could not find client.js in HTML")
|
|
757
|
+
except (URLError, HTTPError) as exc:
|
|
758
|
+
print(f"[DEBUG] Warning: Could not verify static assets: {exc}")
|
|
759
|
+
# Not a hard failure - the main page test is sufficient
|
|
760
|
+
|
|
761
|
+
print("[DEBUG] All default app tests passed!")
|
|
762
|
+
|
|
763
|
+
finally:
|
|
764
|
+
if server is not None:
|
|
765
|
+
print("[DEBUG] Terminating server process")
|
|
766
|
+
server.terminate()
|
|
767
|
+
try:
|
|
768
|
+
server.wait(timeout=15)
|
|
769
|
+
print("[DEBUG] Server terminated cleanly")
|
|
770
|
+
except Exception:
|
|
771
|
+
print("[DEBUG] Server did not terminate cleanly, killing")
|
|
772
|
+
server.kill()
|
|
773
|
+
server.wait(timeout=5)
|
|
774
|
+
time.sleep(1)
|
|
775
|
+
gc.collect()
|
|
776
|
+
|
|
777
|
+
finally:
|
|
778
|
+
print(f"[DEBUG] Restoring working directory to {original_cwd}")
|
|
779
|
+
os.chdir(original_cwd)
|
|
780
|
+
gc.collect()
|