loop-agent-cli 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.
- app/__init__.py +0 -0
- app/agents/__init__.py +0 -0
- app/agents/graph.py +40 -0
- app/agents/nodes.py +245 -0
- app/agents/state.py +19 -0
- app/api/__init__.py +0 -0
- app/api/candidates.py +49 -0
- app/api/dashboard.py +7 -0
- app/api/outreach.py +7 -0
- app/api/pipelines.py +58 -0
- app/api/positions.py +76 -0
- app/api/router.py +14 -0
- app/api/scheduler.py +7 -0
- app/api/skills.py +7 -0
- app/api/system.py +7 -0
- app/core/__init__.py +0 -0
- app/core/config.py +18 -0
- app/core/exception_handler.py +91 -0
- app/core/exceptions.py +33 -0
- app/core/logging.py +19 -0
- app/database/__init__.py +0 -0
- app/database/base.py +4 -0
- app/database/session.py +20 -0
- app/main.py +72 -0
- app/models/__init__.py +0 -0
- app/models/agent_run.py +18 -0
- app/models/candidate.py +28 -0
- app/models/node_log.py +18 -0
- app/models/outreach_log.py +16 -0
- app/models/pipeline.py +21 -0
- app/models/position.py +22 -0
- app/models/scheduler_job.py +16 -0
- app/models/skill.py +13 -0
- app/models/system_config.py +12 -0
- app/repositories/__init__.py +0 -0
- app/repositories/agent_run.py +74 -0
- app/repositories/candidate.py +84 -0
- app/repositories/node_log.py +57 -0
- app/repositories/outreach_log.py +60 -0
- app/repositories/pipeline.py +80 -0
- app/repositories/position.py +67 -0
- app/repositories/scheduler_job.py +74 -0
- app/schemas/__init__.py +0 -0
- app/schemas/agent_run.py +32 -0
- app/schemas/candidate.py +58 -0
- app/schemas/node_log.py +31 -0
- app/schemas/outreach_log.py +28 -0
- app/schemas/pipeline.py +34 -0
- app/schemas/position.py +49 -0
- app/schemas/scheduler_job.py +29 -0
- app/services/__init__.py +0 -0
- app/services/candidate.py +58 -0
- app/services/dashboard.py +230 -0
- app/services/email.py +116 -0
- app/services/health.py +105 -0
- app/services/pipeline.py +75 -0
- app/services/position.py +36 -0
- app/services/runner.py +292 -0
- app/services/scheduler.py +174 -0
- app/services/score.py +155 -0
- app/services/search.py +92 -0
- app/skills/base.py +30 -0
- app/skills/github.py +106 -0
- app/skills/registry.py +51 -0
- app/tests/__init__.py +3 -0
- app/tests/conftest.py +96 -0
- app/tests/generate_report.py +144 -0
- app/tests/test_candidates.py +158 -0
- app/tests/test_dashboard.py +27 -0
- app/tests/test_outreach.py +15 -0
- app/tests/test_pipelines.py +249 -0
- app/tests/test_positions.py +183 -0
- app/tests/test_scheduler.py +15 -0
- app/tests/test_skills.py +15 -0
- app/tests/test_system.py +35 -0
- app/utils/__init__.py +0 -0
- loop_agent_cli/__init__.py +5 -0
- loop_agent_cli/cli.py +728 -0
- loop_agent_cli/container.py +191 -0
- loop_agent_cli-0.1.0.dist-info/METADATA +202 -0
- loop_agent_cli-0.1.0.dist-info/RECORD +84 -0
- loop_agent_cli-0.1.0.dist-info/WHEEL +5 -0
- loop_agent_cli-0.1.0.dist-info/entry_points.txt +2 -0
- loop_agent_cli-0.1.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dashboard API tests
|
|
3
|
+
"""
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.mark.asyncio
|
|
8
|
+
async def test_get_dashboard_summary(client):
|
|
9
|
+
"""Test getting dashboard summary"""
|
|
10
|
+
response = await client.get("/api/v1/dashboard/summary")
|
|
11
|
+
|
|
12
|
+
assert response.status_code == 200
|
|
13
|
+
data = response.json()
|
|
14
|
+
|
|
15
|
+
assert "running_positions" in data
|
|
16
|
+
assert "today_loops" in data
|
|
17
|
+
assert "today_candidates" in data
|
|
18
|
+
assert "today_emails" in data
|
|
19
|
+
assert "today_replies" in data
|
|
20
|
+
assert "today_errors" in data
|
|
21
|
+
|
|
22
|
+
assert isinstance(data["running_positions"], int)
|
|
23
|
+
assert isinstance(data["today_loops"], int)
|
|
24
|
+
assert isinstance(data["today_candidates"], int)
|
|
25
|
+
assert isinstance(data["today_emails"], int)
|
|
26
|
+
assert isinstance(data["today_replies"], int)
|
|
27
|
+
assert isinstance(data["today_errors"], int)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Outreach API tests
|
|
3
|
+
"""
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.mark.asyncio
|
|
8
|
+
async def test_get_outreach_logs(client):
|
|
9
|
+
"""Test getting outreach logs"""
|
|
10
|
+
response = await client.get("/api/v1/outreach/logs")
|
|
11
|
+
|
|
12
|
+
assert response.status_code == 200
|
|
13
|
+
data = response.json()
|
|
14
|
+
|
|
15
|
+
assert isinstance(data, list)
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pipelines API tests
|
|
3
|
+
"""
|
|
4
|
+
import pytest
|
|
5
|
+
import uuid
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.mark.asyncio
|
|
9
|
+
async def test_create_pipeline(client, sample_position_data, sample_candidate_data):
|
|
10
|
+
"""Test creating a new pipeline"""
|
|
11
|
+
# Create position and candidate first
|
|
12
|
+
pos_response = await client.post("/api/v1/positions", json=sample_position_data)
|
|
13
|
+
position_id = pos_response.json()["id"]
|
|
14
|
+
|
|
15
|
+
cand_response = await client.post("/api/v1/candidates", json=sample_candidate_data)
|
|
16
|
+
candidate_id = cand_response.json()["id"]
|
|
17
|
+
|
|
18
|
+
pipeline_data = {
|
|
19
|
+
"position_id": position_id,
|
|
20
|
+
"candidate_id": candidate_id,
|
|
21
|
+
"status": "discovered",
|
|
22
|
+
"score": 85.5,
|
|
23
|
+
"contact_count": 0,
|
|
24
|
+
"candidate_interest": "high",
|
|
25
|
+
"notes": "Strong candidate"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
response = await client.post("/api/v1/pipelines", json=pipeline_data)
|
|
29
|
+
|
|
30
|
+
assert response.status_code == 200
|
|
31
|
+
data = response.json()
|
|
32
|
+
|
|
33
|
+
assert data["position_id"] == position_id
|
|
34
|
+
assert data["candidate_id"] == candidate_id
|
|
35
|
+
assert data["status"] == "discovered"
|
|
36
|
+
assert data["score"] == 85.5
|
|
37
|
+
assert "id" in data
|
|
38
|
+
assert "created_at" in data
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@pytest.mark.asyncio
|
|
42
|
+
async def test_create_duplicate_pipeline(client, sample_position_data, sample_candidate_data):
|
|
43
|
+
"""Test creating a duplicate pipeline returns existing one"""
|
|
44
|
+
pos_response = await client.post("/api/v1/positions", json=sample_position_data)
|
|
45
|
+
position_id = pos_response.json()["id"]
|
|
46
|
+
|
|
47
|
+
cand_response = await client.post("/api/v1/candidates", json=sample_candidate_data)
|
|
48
|
+
candidate_id = cand_response.json()["id"]
|
|
49
|
+
|
|
50
|
+
pipeline_data = {
|
|
51
|
+
"position_id": position_id,
|
|
52
|
+
"candidate_id": candidate_id,
|
|
53
|
+
"status": "discovered"
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# Create first time
|
|
57
|
+
response1 = await client.post("/api/v1/pipelines", json=pipeline_data)
|
|
58
|
+
assert response1.status_code == 200
|
|
59
|
+
pipeline_id_1 = response1.json()["id"]
|
|
60
|
+
|
|
61
|
+
# Create duplicate
|
|
62
|
+
response2 = await client.post("/api/v1/pipelines", json=pipeline_data)
|
|
63
|
+
assert response2.status_code == 200
|
|
64
|
+
pipeline_id_2 = response2.json()["id"]
|
|
65
|
+
|
|
66
|
+
# Should return the same pipeline
|
|
67
|
+
assert pipeline_id_1 == pipeline_id_2
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@pytest.mark.asyncio
|
|
71
|
+
async def test_get_pipelines(client, sample_position_data, sample_candidate_data):
|
|
72
|
+
"""Test getting all pipelines"""
|
|
73
|
+
pos_response = await client.post("/api/v1/positions", json=sample_position_data)
|
|
74
|
+
position_id = pos_response.json()["id"]
|
|
75
|
+
|
|
76
|
+
cand_response = await client.post("/api/v1/candidates", json=sample_candidate_data)
|
|
77
|
+
candidate_id = cand_response.json()["id"]
|
|
78
|
+
|
|
79
|
+
pipeline_data = {
|
|
80
|
+
"position_id": position_id,
|
|
81
|
+
"candidate_id": candidate_id
|
|
82
|
+
}
|
|
83
|
+
await client.post("/api/v1/pipelines", json=pipeline_data)
|
|
84
|
+
|
|
85
|
+
response = await client.get("/api/v1/pipelines")
|
|
86
|
+
|
|
87
|
+
assert response.status_code == 200
|
|
88
|
+
data = response.json()
|
|
89
|
+
|
|
90
|
+
assert isinstance(data, list)
|
|
91
|
+
assert len(data) >= 1
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@pytest.mark.asyncio
|
|
95
|
+
async def test_get_pipelines_with_pagination(client, sample_position_data, sample_candidate_data):
|
|
96
|
+
"""Test getting pipelines with pagination"""
|
|
97
|
+
pos_response = await client.post("/api/v1/positions", json=sample_position_data)
|
|
98
|
+
position_id = pos_response.json()["id"]
|
|
99
|
+
|
|
100
|
+
# Create multiple candidates and pipelines
|
|
101
|
+
for i in range(3):
|
|
102
|
+
cand_data = sample_candidate_data.copy()
|
|
103
|
+
cand_data["source_id"] = str(20000 + i)
|
|
104
|
+
cand_data["github_login"] = f"pipeuser{i}"
|
|
105
|
+
cand_response = await client.post("/api/v1/candidates", json=cand_data)
|
|
106
|
+
candidate_id = cand_response.json()["id"]
|
|
107
|
+
|
|
108
|
+
pipeline_data = {
|
|
109
|
+
"position_id": position_id,
|
|
110
|
+
"candidate_id": candidate_id
|
|
111
|
+
}
|
|
112
|
+
await client.post("/api/v1/pipelines", json=pipeline_data)
|
|
113
|
+
|
|
114
|
+
response = await client.get("/api/v1/pipelines?skip=0&limit=2")
|
|
115
|
+
|
|
116
|
+
assert response.status_code == 200
|
|
117
|
+
data = response.json()
|
|
118
|
+
assert len(data) == 2
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@pytest.mark.asyncio
|
|
122
|
+
async def test_get_pipeline_by_id(client, sample_position_data, sample_candidate_data):
|
|
123
|
+
"""Test getting a specific pipeline by ID"""
|
|
124
|
+
pos_response = await client.post("/api/v1/positions", json=sample_position_data)
|
|
125
|
+
position_id = pos_response.json()["id"]
|
|
126
|
+
|
|
127
|
+
cand_response = await client.post("/api/v1/candidates", json=sample_candidate_data)
|
|
128
|
+
candidate_id = cand_response.json()["id"]
|
|
129
|
+
|
|
130
|
+
pipeline_data = {
|
|
131
|
+
"position_id": position_id,
|
|
132
|
+
"candidate_id": candidate_id
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
create_response = await client.post("/api/v1/pipelines", json=pipeline_data)
|
|
136
|
+
pipeline_id = create_response.json()["id"]
|
|
137
|
+
|
|
138
|
+
response = await client.get(f"/api/v1/pipelines/{pipeline_id}")
|
|
139
|
+
|
|
140
|
+
assert response.status_code == 200
|
|
141
|
+
data = response.json()
|
|
142
|
+
assert data["id"] == pipeline_id
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@pytest.mark.asyncio
|
|
146
|
+
async def test_get_pipeline_not_found(client):
|
|
147
|
+
"""Test getting a non-existent pipeline"""
|
|
148
|
+
fake_id = str(uuid.uuid4())
|
|
149
|
+
response = await client.get(f"/api/v1/pipelines/{fake_id}")
|
|
150
|
+
|
|
151
|
+
assert response.status_code == 404
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@pytest.mark.asyncio
|
|
155
|
+
async def test_update_pipeline(client, sample_position_data, sample_candidate_data):
|
|
156
|
+
"""Test updating a pipeline"""
|
|
157
|
+
pos_response = await client.post("/api/v1/positions", json=sample_position_data)
|
|
158
|
+
position_id = pos_response.json()["id"]
|
|
159
|
+
|
|
160
|
+
cand_response = await client.post("/api/v1/candidates", json=sample_candidate_data)
|
|
161
|
+
candidate_id = cand_response.json()["id"]
|
|
162
|
+
|
|
163
|
+
pipeline_data = {
|
|
164
|
+
"position_id": position_id,
|
|
165
|
+
"candidate_id": candidate_id
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
create_response = await client.post("/api/v1/pipelines", json=pipeline_data)
|
|
169
|
+
pipeline_id = create_response.json()["id"]
|
|
170
|
+
|
|
171
|
+
update_data = {"status": "contacted", "score": 90.0, "notes": "Updated notes"}
|
|
172
|
+
response = await client.put(f"/api/v1/pipelines/{pipeline_id}", json=update_data)
|
|
173
|
+
|
|
174
|
+
assert response.status_code == 200
|
|
175
|
+
data = response.json()
|
|
176
|
+
assert data["status"] == "contacted"
|
|
177
|
+
assert data["score"] == 90.0
|
|
178
|
+
assert data["notes"] == "Updated notes"
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@pytest.mark.asyncio
|
|
182
|
+
async def test_update_pipeline_not_found(client):
|
|
183
|
+
"""Test updating a non-existent pipeline"""
|
|
184
|
+
fake_id = str(uuid.uuid4())
|
|
185
|
+
update_data = {"status": "contacted"}
|
|
186
|
+
response = await client.put(f"/api/v1/pipelines/{fake_id}", json=update_data)
|
|
187
|
+
|
|
188
|
+
assert response.status_code == 404
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@pytest.mark.asyncio
|
|
192
|
+
async def test_update_pipeline_status(client, sample_position_data, sample_candidate_data):
|
|
193
|
+
"""Test updating pipeline status"""
|
|
194
|
+
pos_response = await client.post("/api/v1/positions", json=sample_position_data)
|
|
195
|
+
position_id = pos_response.json()["id"]
|
|
196
|
+
|
|
197
|
+
cand_response = await client.post("/api/v1/candidates", json=sample_candidate_data)
|
|
198
|
+
candidate_id = cand_response.json()["id"]
|
|
199
|
+
|
|
200
|
+
pipeline_data = {
|
|
201
|
+
"position_id": position_id,
|
|
202
|
+
"candidate_id": candidate_id
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
create_response = await client.post("/api/v1/pipelines", json=pipeline_data)
|
|
206
|
+
pipeline_id = create_response.json()["id"]
|
|
207
|
+
|
|
208
|
+
response = await client.put(f"/api/v1/pipelines/{pipeline_id}/status?status=interview")
|
|
209
|
+
|
|
210
|
+
assert response.status_code == 200
|
|
211
|
+
data = response.json()
|
|
212
|
+
assert data["status"] == "interview"
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@pytest.mark.asyncio
|
|
216
|
+
async def test_delete_pipeline(client, sample_position_data, sample_candidate_data):
|
|
217
|
+
"""Test deleting a pipeline"""
|
|
218
|
+
pos_response = await client.post("/api/v1/positions", json=sample_position_data)
|
|
219
|
+
position_id = pos_response.json()["id"]
|
|
220
|
+
|
|
221
|
+
cand_response = await client.post("/api/v1/candidates", json=sample_candidate_data)
|
|
222
|
+
candidate_id = cand_response.json()["id"]
|
|
223
|
+
|
|
224
|
+
pipeline_data = {
|
|
225
|
+
"position_id": position_id,
|
|
226
|
+
"candidate_id": candidate_id
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
create_response = await client.post("/api/v1/pipelines", json=pipeline_data)
|
|
230
|
+
pipeline_id = create_response.json()["id"]
|
|
231
|
+
|
|
232
|
+
response = await client.delete(f"/api/v1/pipelines/{pipeline_id}")
|
|
233
|
+
|
|
234
|
+
assert response.status_code == 200
|
|
235
|
+
data = response.json()
|
|
236
|
+
assert data["message"] == "Pipeline deleted successfully"
|
|
237
|
+
|
|
238
|
+
# Verify it's deleted
|
|
239
|
+
get_response = await client.get(f"/api/v1/pipelines/{pipeline_id}")
|
|
240
|
+
assert get_response.status_code == 404
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
@pytest.mark.asyncio
|
|
244
|
+
async def test_delete_pipeline_not_found(client):
|
|
245
|
+
"""Test deleting a non-existent pipeline"""
|
|
246
|
+
fake_id = str(uuid.uuid4())
|
|
247
|
+
response = await client.delete(f"/api/v1/pipelines/{fake_id}")
|
|
248
|
+
|
|
249
|
+
assert response.status_code == 404
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Positions API tests
|
|
3
|
+
"""
|
|
4
|
+
import pytest
|
|
5
|
+
import uuid
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.mark.asyncio
|
|
9
|
+
async def test_create_position(client, sample_position_data):
|
|
10
|
+
"""Test creating a new position"""
|
|
11
|
+
response = await client.post("/api/v1/positions", json=sample_position_data)
|
|
12
|
+
|
|
13
|
+
assert response.status_code == 200
|
|
14
|
+
data = response.json()
|
|
15
|
+
|
|
16
|
+
assert data["title"] == sample_position_data["title"]
|
|
17
|
+
assert data["company"] == sample_position_data["company"]
|
|
18
|
+
assert data["status"] == "active"
|
|
19
|
+
assert "id" in data
|
|
20
|
+
assert "created_at" in data
|
|
21
|
+
assert "updated_at" in data
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.mark.asyncio
|
|
25
|
+
async def test_get_positions(client, sample_position_data):
|
|
26
|
+
"""Test getting all positions"""
|
|
27
|
+
# Create a position first
|
|
28
|
+
await client.post("/api/v1/positions", json=sample_position_data)
|
|
29
|
+
|
|
30
|
+
response = await client.get("/api/v1/positions")
|
|
31
|
+
|
|
32
|
+
assert response.status_code == 200
|
|
33
|
+
data = response.json()
|
|
34
|
+
|
|
35
|
+
assert isinstance(data, list)
|
|
36
|
+
assert len(data) >= 1
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@pytest.mark.asyncio
|
|
40
|
+
async def test_get_positions_with_pagination(client, sample_position_data):
|
|
41
|
+
"""Test getting positions with pagination"""
|
|
42
|
+
# Create multiple positions
|
|
43
|
+
for i in range(3):
|
|
44
|
+
data = sample_position_data.copy()
|
|
45
|
+
data["title"] = f"Position {i}"
|
|
46
|
+
await client.post("/api/v1/positions", json=data)
|
|
47
|
+
|
|
48
|
+
response = await client.get("/api/v1/positions?skip=0&limit=2")
|
|
49
|
+
|
|
50
|
+
assert response.status_code == 200
|
|
51
|
+
data = response.json()
|
|
52
|
+
assert len(data) == 2
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@pytest.mark.asyncio
|
|
56
|
+
async def test_get_positions_with_status_filter(client, sample_position_data):
|
|
57
|
+
"""Test getting positions with status filter"""
|
|
58
|
+
await client.post("/api/v1/positions", json=sample_position_data)
|
|
59
|
+
|
|
60
|
+
response = await client.get("/api/v1/positions?status=active")
|
|
61
|
+
|
|
62
|
+
assert response.status_code == 200
|
|
63
|
+
data = response.json()
|
|
64
|
+
assert all(pos["status"] == "active" for pos in data)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@pytest.mark.asyncio
|
|
68
|
+
async def test_get_position_by_id(client, sample_position_data):
|
|
69
|
+
"""Test getting a specific position by ID"""
|
|
70
|
+
create_response = await client.post("/api/v1/positions", json=sample_position_data)
|
|
71
|
+
position_id = create_response.json()["id"]
|
|
72
|
+
|
|
73
|
+
response = await client.get(f"/api/v1/positions/{position_id}")
|
|
74
|
+
|
|
75
|
+
assert response.status_code == 200
|
|
76
|
+
data = response.json()
|
|
77
|
+
assert data["id"] == position_id
|
|
78
|
+
assert data["title"] == sample_position_data["title"]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@pytest.mark.asyncio
|
|
82
|
+
async def test_get_position_not_found(client):
|
|
83
|
+
"""Test getting a non-existent position"""
|
|
84
|
+
fake_id = str(uuid.uuid4())
|
|
85
|
+
response = await client.get(f"/api/v1/positions/{fake_id}")
|
|
86
|
+
|
|
87
|
+
assert response.status_code == 404
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@pytest.mark.asyncio
|
|
91
|
+
async def test_update_position(client, sample_position_data):
|
|
92
|
+
"""Test updating a position"""
|
|
93
|
+
create_response = await client.post("/api/v1/positions", json=sample_position_data)
|
|
94
|
+
position_id = create_response.json()["id"]
|
|
95
|
+
|
|
96
|
+
update_data = {"title": "Updated Title", "company": "Updated Company"}
|
|
97
|
+
response = await client.put(f"/api/v1/positions/{position_id}", json=update_data)
|
|
98
|
+
|
|
99
|
+
assert response.status_code == 200
|
|
100
|
+
data = response.json()
|
|
101
|
+
assert data["title"] == "Updated Title"
|
|
102
|
+
assert data["company"] == "Updated Company"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@pytest.mark.asyncio
|
|
106
|
+
async def test_update_position_not_found(client):
|
|
107
|
+
"""Test updating a non-existent position"""
|
|
108
|
+
fake_id = str(uuid.uuid4())
|
|
109
|
+
update_data = {"title": "Updated Title"}
|
|
110
|
+
response = await client.put(f"/api/v1/positions/{fake_id}", json=update_data)
|
|
111
|
+
|
|
112
|
+
assert response.status_code == 404
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@pytest.mark.asyncio
|
|
116
|
+
async def test_delete_position(client, sample_position_data):
|
|
117
|
+
"""Test deleting a position"""
|
|
118
|
+
create_response = await client.post("/api/v1/positions", json=sample_position_data)
|
|
119
|
+
position_id = create_response.json()["id"]
|
|
120
|
+
|
|
121
|
+
response = await client.delete(f"/api/v1/positions/{position_id}")
|
|
122
|
+
|
|
123
|
+
assert response.status_code == 200
|
|
124
|
+
data = response.json()
|
|
125
|
+
assert data["message"] == "Position deleted successfully"
|
|
126
|
+
|
|
127
|
+
# Verify it's deleted
|
|
128
|
+
get_response = await client.get(f"/api/v1/positions/{position_id}")
|
|
129
|
+
assert get_response.status_code == 404
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@pytest.mark.asyncio
|
|
133
|
+
async def test_delete_position_not_found(client):
|
|
134
|
+
"""Test deleting a non-existent position"""
|
|
135
|
+
fake_id = str(uuid.uuid4())
|
|
136
|
+
response = await client.delete(f"/api/v1/positions/{fake_id}")
|
|
137
|
+
|
|
138
|
+
assert response.status_code == 404
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@pytest.mark.asyncio
|
|
142
|
+
async def test_pause_position(client, sample_position_data):
|
|
143
|
+
"""Test pausing a position"""
|
|
144
|
+
create_response = await client.post("/api/v1/positions", json=sample_position_data)
|
|
145
|
+
position_id = create_response.json()["id"]
|
|
146
|
+
|
|
147
|
+
response = await client.post(f"/api/v1/positions/{position_id}/pause")
|
|
148
|
+
|
|
149
|
+
assert response.status_code == 200
|
|
150
|
+
data = response.json()
|
|
151
|
+
assert data["status"] == "paused"
|
|
152
|
+
assert data["loop_enabled"] == False
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@pytest.mark.asyncio
|
|
156
|
+
async def test_resume_position(client, sample_position_data):
|
|
157
|
+
"""Test resuming a paused position"""
|
|
158
|
+
create_response = await client.post("/api/v1/positions", json=sample_position_data)
|
|
159
|
+
position_id = create_response.json()["id"]
|
|
160
|
+
|
|
161
|
+
# Pause first
|
|
162
|
+
await client.post(f"/api/v1/positions/{position_id}/pause")
|
|
163
|
+
|
|
164
|
+
# Then resume
|
|
165
|
+
response = await client.post(f"/api/v1/positions/{position_id}/resume")
|
|
166
|
+
|
|
167
|
+
assert response.status_code == 200
|
|
168
|
+
data = response.json()
|
|
169
|
+
assert data["status"] == "active"
|
|
170
|
+
assert data["loop_enabled"] == True
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@pytest.mark.asyncio
|
|
174
|
+
async def test_close_position(client, sample_position_data):
|
|
175
|
+
"""Test closing a position"""
|
|
176
|
+
create_response = await client.post("/api/v1/positions", json=sample_position_data)
|
|
177
|
+
position_id = create_response.json()["id"]
|
|
178
|
+
|
|
179
|
+
response = await client.post(f"/api/v1/positions/{position_id}/close")
|
|
180
|
+
|
|
181
|
+
assert response.status_code == 200
|
|
182
|
+
data = response.json()
|
|
183
|
+
assert data["status"] == "closed"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Scheduler API tests
|
|
3
|
+
"""
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.mark.asyncio
|
|
8
|
+
async def test_get_scheduler_jobs(client):
|
|
9
|
+
"""Test getting scheduler jobs"""
|
|
10
|
+
response = await client.get("/api/v1/scheduler/jobs")
|
|
11
|
+
|
|
12
|
+
assert response.status_code == 200
|
|
13
|
+
data = response.json()
|
|
14
|
+
|
|
15
|
+
assert isinstance(data, list)
|
app/tests/test_skills.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Skills API tests
|
|
3
|
+
"""
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.mark.asyncio
|
|
8
|
+
async def test_get_skills(client):
|
|
9
|
+
"""Test getting skills"""
|
|
10
|
+
response = await client.get("/api/v1/skills")
|
|
11
|
+
|
|
12
|
+
assert response.status_code == 200
|
|
13
|
+
data = response.json()
|
|
14
|
+
|
|
15
|
+
assert isinstance(data, list)
|
app/tests/test_system.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""
|
|
2
|
+
System API tests
|
|
3
|
+
"""
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.mark.asyncio
|
|
8
|
+
async def test_get_system_config(client):
|
|
9
|
+
"""Test getting system configuration"""
|
|
10
|
+
response = await client.get("/api/v1/system/config")
|
|
11
|
+
|
|
12
|
+
assert response.status_code == 200
|
|
13
|
+
data = response.json()
|
|
14
|
+
|
|
15
|
+
assert isinstance(data, dict)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.mark.asyncio
|
|
19
|
+
async def test_root_endpoint(client):
|
|
20
|
+
"""Test root endpoint"""
|
|
21
|
+
response = await client.get("/")
|
|
22
|
+
|
|
23
|
+
assert response.status_code == 200
|
|
24
|
+
data = response.json()
|
|
25
|
+
assert data["message"] == "Recruiting Loop Agent is running!"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@pytest.mark.asyncio
|
|
29
|
+
async def test_health_check(client):
|
|
30
|
+
"""Test health check endpoint"""
|
|
31
|
+
response = await client.get("/health")
|
|
32
|
+
|
|
33
|
+
assert response.status_code == 200
|
|
34
|
+
data = response.json()
|
|
35
|
+
assert data["status"] == "ok"
|
app/utils/__init__.py
ADDED
|
File without changes
|