forked from komalharshita/DevPath
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_basic.py
More file actions
321 lines (243 loc) · 10 KB
/
Copy pathtest_basic.py
File metadata and controls
321 lines (243 loc) · 10 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
# tests/test_basic.py
# Basic tests for core DevPath functionality.
#
# Run with: python -m pytest tests/
# Or: python tests/test_basic.py
#
# These tests check that:
# - The projects dataset loads without errors
# - The recommendation engine returns sensible results
# - Input validation catches bad data
# - All main HTTP routes return the expected status codes
import sys
import os
# Allow imports from the project root when running tests directly
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from utils.data_loader import load_all_projects, find_project_by_id, clear_cache
from utils.recommender import (
get_recommendations,
validate_recommendation_inputs,
parse_skills,
score_single_project,
)
from app import app, internal_server_error
# ============================================================
# Test setup
# ============================================================
def setup_module():
"""Clear the data cache before running the test suite to ensure clean state."""
clear_cache()
# ============================================================
# Data loader tests
# ============================================================
def test_projects_json_loads():
"""The data file must exist and contain at least one project."""
projects = load_all_projects()
assert isinstance(projects, list), "Expected a list of projects"
assert len(projects) > 0, "Project list must not be empty"
def test_each_project_has_required_fields():
"""Every project must have the fields the UI depends on."""
required = ["id", "title", "skills", "level", "interest", "time",
"description", "features", "tech_stack", "roadmap",
"resources", "starter_code"]
for project in load_all_projects():
for field in required:
assert field in project, f"Project '{project.get('title')}' is missing field: {field}"
def test_find_project_by_id_found():
"""find_project_by_id should return the correct project when the ID exists."""
project = find_project_by_id(1)
assert project is not None, "Expected project with id=1 to exist"
assert project["id"] == 1
def test_find_project_by_id_missing():
"""find_project_by_id should return None for an ID that does not exist."""
result = find_project_by_id(99999)
assert result is None, "Expected None for a non-existent project ID"
# ============================================================
# Recommender utility tests
# ============================================================
def test_parse_skills_basic():
"""parse_skills should split on commas and lowercase each entry."""
result = parse_skills("Python, HTML, CSS")
assert result == ["python", "html", "css"]
def test_parse_skills_empty_string():
"""parse_skills should return an empty list for blank input."""
assert parse_skills("") == []
assert parse_skills(" ") == []
def test_parse_skills_single_entry():
"""parse_skills should handle a single skill with no commas."""
assert parse_skills("JavaScript") == ["javascript"]
def test_score_single_project_full_match():
"""A project that matches all four criteria should receive the maximum score."""
project = {
"skills": ["Python"],
"level": "Beginner",
"interest": "Data",
"time": "Low"
}
score = score_single_project(
project,
user_skills=["python"],
level="Beginner",
interest="Data",
time_availability="Low"
)
# 1 skill match (3) + level (2) + interest (2) + time (1) = 8
assert score == 8, f"Expected 8 but got {score}"
def test_score_single_project_no_match():
"""A project with no overlap should score zero."""
project = {
"skills": ["Rust"],
"level": "Advanced",
"interest": "Games",
"time": "High"
}
score = score_single_project(
project,
user_skills=["python"],
level="Beginner",
interest="Data",
time_availability="Low"
)
assert score == 0, f"Expected 0 but got {score}"
def test_get_recommendations_returns_results():
"""Python + Beginner + Data + Low should always return at least one result."""
results = get_recommendations("Python", "Beginner", "Data", "Low")
assert len(results) > 0, "Expected at least one recommendation"
def test_get_recommendations_max_three():
"""The engine must never return more than three results."""
results = get_recommendations("Python, JavaScript, HTML", "Beginner", "Web", "Low")
assert len(results) <= 3, f"Expected at most 3 results, got {len(results)}"
def test_get_recommendations_no_match_returns_empty():
"""A very unlikely skill/interest combo should return an empty list."""
results = get_recommendations("Rust", "Advanced", "Games", "High")
# Rust and Games are not in the dataset so this should be empty or minimal
assert isinstance(results, list)
def test_get_recommendations_result_format():
"""Each returned project must be a dict with at least a title and id."""
results = get_recommendations("Python", "Beginner", "Data", "Low")
for project in results:
assert "id" in project
assert "title" in project
# ============================================================
# Input validation tests
# ============================================================
def test_validate_all_valid():
"""No errors should be returned when all fields are provided."""
errors = validate_recommendation_inputs("Python", "Beginner", "Web", "Low")
assert errors == [], f"Unexpected errors: {errors}"
def test_validate_missing_skills():
"""An empty skills field must produce an error."""
errors = validate_recommendation_inputs("", "Beginner", "Web", "Low")
assert len(errors) > 0
def test_validate_missing_level():
errors = validate_recommendation_inputs("Python", "", "Web", "Low")
assert len(errors) > 0
def test_validate_missing_interest():
errors = validate_recommendation_inputs("Python", "Beginner", "", "Low")
assert len(errors) > 0
def test_validate_missing_time():
errors = validate_recommendation_inputs("Python", "Beginner", "Web", "")
assert len(errors) > 0
def test_validate_all_missing():
"""All four fields missing should produce four errors."""
errors = validate_recommendation_inputs("", "", "", "")
assert len(errors) == 4
# ============================================================
# HTTP route tests (using Flask test client)
# ============================================================
def get_client():
"""Return a Flask test client with testing mode enabled."""
app.config["TESTING"] = True
return app.test_client()
def test_home_route():
client = get_client()
response = client.get("/")
assert response.status_code == 200
def test_recommend_api_valid():
client = get_client()
response = client.post("/api/recommend", json={
"skills": "Python",
"level": "Beginner",
"interest": "Data",
"time": "Low"
})
assert response.status_code == 200
data = response.get_json()
assert "projects" in data
assert len(data["projects"]) > 0
def test_recommend_api_missing_field():
"""The API should return 400 when a required field is missing."""
client = get_client()
response = client.post("/api/recommend", json={
"skills": "",
"level": "Beginner",
"interest": "Data",
"time": "Low"
})
assert response.status_code in (400, 415)
assert "error" in response.get_json()
def test_recommend_api_empty_body():
"""The API should return 400 when the body is not valid JSON."""
client = get_client()
response = client.post("/api/recommend",
data="not json",
content_type="text/plain")
assert response.status_code in (400, 415)
def test_project_detail_found():
client = get_client()
response = client.get("/project/1")
assert response.status_code == 200
def test_project_detail_not_found():
client = get_client()
response = client.get("/project/99999")
assert response.status_code == 404
def test_internal_server_error_page():
"""The 500 handler should render the friendly internal error template."""
with app.app_context():
rendered_page, status_code = internal_server_error(Exception("Test error"))
assert status_code == 500
assert "Internal Server Error" in rendered_page
assert "Back to Home" in rendered_page
def test_view_code_found():
client = get_client()
response = client.get("/project/1/code")
assert response.status_code == 200
data = response.get_json()
assert "code" in data
assert "filename" in data
assert len(data["code"]) > 0
def test_download_code_found():
client = get_client()
response = client.get("/project/1/download")
assert response.status_code == 200
def test_health_check():
client = get_client()
response = client.get("/health")
assert response.status_code == 200
data = response.get_json()
assert "status" in data
assert "version" in data
assert data["status"] == "ok"
from utils.recommender import SCORING_WEIGHTS
def test_scoring_weights_has_all_keys():
"""Verify SCORING_WEIGHTS contains exactly the four expected keys."""
expected_keys = {"skill", "level", "interest", "time"}
assert set(SCORING_WEIGHTS.keys()) == expected_keys
# ============================================================
# Run tests directly (no pytest required)
# ============================================================
if __name__ == "__main__":
test_functions = [v for k, v in list(globals().items()) if k.startswith("test_")]
passed = 0
failed = 0
for fn in test_functions:
try:
fn()
print(f" PASS {fn.__name__}")
passed += 1
except Exception as exc:
print(f" FAIL {fn.__name__}: {exc}")
failed += 1
print(f"\n{passed} passed, {failed} failed out of {passed + failed} tests")
if failed > 0:
sys.exit(1)