log4lab 0.1.0__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.
tests/test_server.py ADDED
@@ -0,0 +1,214 @@
1
+ """Tests for Log4Lab server endpoints."""
2
+ import json
3
+ import tempfile
4
+ from pathlib import Path
5
+
6
+ import pytest
7
+ from fastapi.testclient import TestClient
8
+
9
+ from log4lab import server
10
+
11
+
12
+ @pytest.fixture
13
+ def temp_log_file():
14
+ """Create a temporary log file with test data."""
15
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.jsonl', delete=False) as f:
16
+ # Write test log entries
17
+ logs = [
18
+ {
19
+ "time": "2025-10-24T10:00:00Z",
20
+ "level": "INFO",
21
+ "section": "test",
22
+ "message": "First log entry",
23
+ "run_name": "test_run",
24
+ "run_id": "run_001",
25
+ "group": "test_group"
26
+ },
27
+ {
28
+ "time": "2025-10-24T10:05:00Z",
29
+ "level": "ERROR",
30
+ "section": "test",
31
+ "message": "Error log entry",
32
+ "run_name": "test_run",
33
+ "run_id": "run_001"
34
+ },
35
+ {
36
+ "time": "2025-10-24T11:00:00Z",
37
+ "level": "INFO",
38
+ "section": "another",
39
+ "message": "Different run",
40
+ "run_name": "test_run",
41
+ "run_id": "run_002"
42
+ },
43
+ {
44
+ "time": "2025-10-24T12:00:00Z",
45
+ "level": "DEBUG",
46
+ "section": "debug",
47
+ "message": "Debug message",
48
+ "run_name": "another_run",
49
+ "run_id": "run_003"
50
+ }
51
+ ]
52
+ for log in logs:
53
+ f.write(json.dumps(log) + '\n')
54
+ temp_path = Path(f.name)
55
+
56
+ yield temp_path
57
+
58
+ # Cleanup
59
+ temp_path.unlink()
60
+
61
+
62
+ @pytest.fixture
63
+ def client(temp_log_file):
64
+ """Create a test client with a temporary log file."""
65
+ server.set_log_path(temp_log_file)
66
+ return TestClient(server.app)
67
+
68
+
69
+ def test_index_page(client):
70
+ """Test that the index page loads successfully."""
71
+ response = client.get("/")
72
+ assert response.status_code == 200
73
+ assert "Log4Lab" in response.text
74
+ assert "Live" in response.text
75
+
76
+
77
+ def test_runs_page(client):
78
+ """Test that the runs index page loads successfully."""
79
+ response = client.get("/runs")
80
+ assert response.status_code == 200
81
+ assert "Log4Lab" in response.text
82
+ assert "Runs Index" in response.text
83
+
84
+
85
+ def test_api_runs_endpoint(client, temp_log_file):
86
+ """Test the /api/runs endpoint returns correct data structure."""
87
+ response = client.get("/api/runs")
88
+ assert response.status_code == 200
89
+
90
+ data = response.json()
91
+ assert "runs" in data
92
+
93
+ # Check that we have the expected run names
94
+ assert "test_run" in data["runs"]
95
+ assert "another_run" in data["runs"]
96
+
97
+ # Check test_run structure
98
+ test_run = data["runs"]["test_run"]
99
+ assert "total" in test_run
100
+ assert "run_ids" in test_run
101
+ assert test_run["total"] == 3 # Three entries for test_run
102
+
103
+ # Check run_ids structure
104
+ assert len(test_run["run_ids"]) == 2 # run_001 and run_002
105
+
106
+ run_id_001 = next(r for r in test_run["run_ids"] if r["run_id"] == "run_001")
107
+ assert run_id_001["count"] == 2
108
+ assert run_id_001["earliest"] == "2025-10-24T10:00:00Z"
109
+ assert run_id_001["latest"] == "2025-10-24T10:05:00Z"
110
+
111
+ run_id_002 = next(r for r in test_run["run_ids"] if r["run_id"] == "run_002")
112
+ assert run_id_002["count"] == 1
113
+ assert run_id_002["earliest"] == "2025-10-24T11:00:00Z"
114
+
115
+
116
+ def test_api_runs_empty_file():
117
+ """Test /api/runs with an empty log file."""
118
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.jsonl', delete=False) as f:
119
+ temp_path = Path(f.name)
120
+
121
+ try:
122
+ server.set_log_path(temp_path)
123
+ client = TestClient(server.app)
124
+
125
+ response = client.get("/api/runs")
126
+ assert response.status_code == 200
127
+
128
+ data = response.json()
129
+ assert data["runs"] == {}
130
+ finally:
131
+ temp_path.unlink()
132
+
133
+
134
+ def test_api_runs_no_run_fields():
135
+ """Test /api/runs with logs that don't have run_name/run_id fields."""
136
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.jsonl', delete=False) as f:
137
+ logs = [
138
+ {"time": "2025-10-24T10:00:00Z", "level": "INFO", "message": "No run info"},
139
+ {"time": "2025-10-24T10:05:00Z", "level": "ERROR", "message": "Another entry"}
140
+ ]
141
+ for log in logs:
142
+ f.write(json.dumps(log) + '\n')
143
+ temp_path = Path(f.name)
144
+
145
+ try:
146
+ server.set_log_path(temp_path)
147
+ client = TestClient(server.app)
148
+
149
+ response = client.get("/api/runs")
150
+ assert response.status_code == 200
151
+
152
+ data = response.json()
153
+ assert data["runs"] == {}
154
+ finally:
155
+ temp_path.unlink()
156
+
157
+
158
+ def test_cache_file_serving(client, temp_log_file):
159
+ """Test serving cache files from the log directory."""
160
+ # Create a test file in the same directory as the log file
161
+ log_dir = temp_log_file.parent
162
+ test_file = log_dir / "test_artifact.txt"
163
+ test_file.write_text("Test artifact content")
164
+
165
+ try:
166
+ response = client.get("/cache/test_artifact.txt")
167
+ assert response.status_code == 200
168
+ assert response.text == "Test artifact content"
169
+ finally:
170
+ test_file.unlink()
171
+
172
+
173
+ def test_cache_file_not_found(client):
174
+ """Test that non-existent cache files return 404."""
175
+ response = client.get("/cache/nonexistent.txt")
176
+ assert response.status_code == 404
177
+
178
+
179
+ def test_cache_file_path_traversal_protection(client):
180
+ """Test that path traversal attacks are blocked."""
181
+ response = client.get("/cache/../../../etc/passwd")
182
+ # Should return an error code (403, 400, or 404 if path doesn't resolve)
183
+ assert response.status_code in [403, 400, 404]
184
+ # Most importantly, should NOT return 200
185
+ assert response.status_code != 200
186
+
187
+
188
+ def test_get_set_log_path(temp_log_file):
189
+ """Test the get_log_path and set_log_path functions."""
190
+ server.set_log_path(temp_log_file)
191
+ assert server.get_log_path() == temp_log_file
192
+
193
+
194
+ def test_invalid_json_lines(client):
195
+ """Test that invalid JSON lines are skipped gracefully."""
196
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.jsonl', delete=False) as f:
197
+ f.write('{"valid": "json"}\n')
198
+ f.write('invalid json line\n')
199
+ f.write('{"another": "valid", "run_name": "test", "run_id": "1"}\n')
200
+ temp_path = Path(f.name)
201
+
202
+ try:
203
+ server.set_log_path(temp_path)
204
+ client = TestClient(server.app)
205
+
206
+ response = client.get("/api/runs")
207
+ assert response.status_code == 200
208
+
209
+ data = response.json()
210
+ # Should only count the valid entries with run info
211
+ assert len(data["runs"]) == 1
212
+ assert "test" in data["runs"]
213
+ finally:
214
+ temp_path.unlink()