@theihtisham/dev-pulse 1.0.0 → 1.1.0
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.
- package/.editorconfig +12 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +43 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +33 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +18 -0
- package/.github/dependabot.yml +16 -0
- package/.github/workflows/ci.yml +33 -0
- package/CODE_OF_CONDUCT.md +27 -0
- package/Dockerfile +8 -0
- package/LICENSE +21 -21
- package/README.md +135 -39
- package/SECURITY.md +22 -0
- package/devpulse/__init__.py +4 -4
- package/devpulse/api/__init__.py +1 -1
- package/devpulse/api/app.py +371 -371
- package/devpulse/cli/__init__.py +1 -1
- package/devpulse/cli/dashboard.py +131 -131
- package/devpulse/cli/main.py +678 -678
- package/devpulse/cli/render.py +175 -175
- package/devpulse/core/__init__.py +34 -34
- package/devpulse/core/analytics.py +487 -487
- package/devpulse/core/config.py +77 -77
- package/devpulse/core/database.py +612 -612
- package/devpulse/core/github_client.py +281 -281
- package/devpulse/core/models.py +142 -142
- package/devpulse/core/report_generator.py +454 -454
- package/devpulse/static/.gitkeep +1 -1
- package/devpulse/templates/report.html +64 -64
- package/package.json +35 -35
- package/pyproject.toml +80 -80
- package/requirements.txt +14 -14
- package/tests/__init__.py +1 -1
- package/tests/conftest.py +208 -208
- package/tests/test_analytics.py +284 -284
- package/tests/test_api.py +313 -313
- package/tests/test_cli.py +204 -204
- package/tests/test_config.py +47 -47
- package/tests/test_database.py +255 -255
- package/tests/test_models.py +107 -107
- package/tests/test_report_generator.py +173 -173
- package/jest.config.js +0 -7
package/tests/test_database.py
CHANGED
|
@@ -1,255 +1,255 @@
|
|
|
1
|
-
"""Tests for the Database layer."""
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
from datetime import datetime
|
|
5
|
-
|
|
6
|
-
import pytest
|
|
7
|
-
|
|
8
|
-
from devpulse.core.database import Database
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class TestDatabaseInit:
|
|
12
|
-
"""Test database initialization."""
|
|
13
|
-
|
|
14
|
-
def test_creates_tables(self, db):
|
|
15
|
-
"""Database should initialize with all required tables."""
|
|
16
|
-
conn = db._connect()
|
|
17
|
-
tables = conn.execute(
|
|
18
|
-
"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
|
|
19
|
-
).fetchall()
|
|
20
|
-
table_names = {t["name"] for t in tables}
|
|
21
|
-
conn.close()
|
|
22
|
-
|
|
23
|
-
expected = {
|
|
24
|
-
"commits", "pull_requests", "issues", "reviews",
|
|
25
|
-
"goals", "reports", "sprint_snapshots", "code_quality",
|
|
26
|
-
}
|
|
27
|
-
assert expected.issubset(table_names), f"Missing tables: {expected - table_names}"
|
|
28
|
-
|
|
29
|
-
def test_idempotent_init(self, db):
|
|
30
|
-
"""Running _init_db twice should not fail."""
|
|
31
|
-
db._init_db()
|
|
32
|
-
db._init_db() # Should not raise
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class TestCommitOperations:
|
|
36
|
-
"""Test commit CRUD operations."""
|
|
37
|
-
|
|
38
|
-
def test_insert_and_retrieve_commits(self, db, sample_commits):
|
|
39
|
-
count = db.upsert_commits(sample_commits)
|
|
40
|
-
assert count == 4
|
|
41
|
-
|
|
42
|
-
commits = db.get_commits()
|
|
43
|
-
assert len(commits) == 4
|
|
44
|
-
|
|
45
|
-
def test_upsert_commits_idempotent(self, db, sample_commits):
|
|
46
|
-
db.upsert_commits(sample_commits)
|
|
47
|
-
# Re-upserting same data should not duplicate rows
|
|
48
|
-
db.upsert_commits(sample_commits)
|
|
49
|
-
|
|
50
|
-
commits = db.get_commits()
|
|
51
|
-
assert len(commits) == 4 # No duplicates
|
|
52
|
-
|
|
53
|
-
def test_filter_by_repo(self, db, sample_commits):
|
|
54
|
-
db.upsert_commits(sample_commits)
|
|
55
|
-
commits = db.get_commits(repo="testorg/testrepo")
|
|
56
|
-
assert len(commits) == 4
|
|
57
|
-
|
|
58
|
-
commits = db.get_commits(repo="nonexistent/repo")
|
|
59
|
-
assert len(commits) == 0
|
|
60
|
-
|
|
61
|
-
def test_filter_by_author(self, db, sample_commits):
|
|
62
|
-
db.upsert_commits(sample_commits)
|
|
63
|
-
alice = db.get_commits(author="Alice")
|
|
64
|
-
assert len(alice) == 3
|
|
65
|
-
|
|
66
|
-
bob = db.get_commits(author="Bob")
|
|
67
|
-
assert len(bob) == 1
|
|
68
|
-
|
|
69
|
-
def test_filter_by_date_range(self, db, sample_commits):
|
|
70
|
-
db.upsert_commits(sample_commits)
|
|
71
|
-
commits = db.get_commits(since="2026-04-02", until="2026-04-02")
|
|
72
|
-
assert len(commits) == 1
|
|
73
|
-
assert commits[0]["author"] == "Bob"
|
|
74
|
-
|
|
75
|
-
def test_commit_count_by_day(self, db, sample_commits):
|
|
76
|
-
db.upsert_commits(sample_commits)
|
|
77
|
-
counts = db.get_commit_count_by_day(days=30)
|
|
78
|
-
assert len(counts) == 3 # 3 unique dates
|
|
79
|
-
# April 1 should have 2 commits
|
|
80
|
-
apr1 = [c for c in counts if c["day"] == "2026-04-01"]
|
|
81
|
-
assert len(apr1) == 1
|
|
82
|
-
assert apr1[0]["count"] == 2
|
|
83
|
-
|
|
84
|
-
def test_commit_limit(self, db, sample_commits):
|
|
85
|
-
db.upsert_commits(sample_commits)
|
|
86
|
-
commits = db.get_commits(limit=2)
|
|
87
|
-
assert len(commits) == 2
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
class TestPullRequestOperations:
|
|
91
|
-
"""Test pull request CRUD operations."""
|
|
92
|
-
|
|
93
|
-
def test_insert_and_retrieve_prs(self, db, sample_prs):
|
|
94
|
-
count = db.upsert_pull_requests(sample_prs)
|
|
95
|
-
assert count == 3
|
|
96
|
-
|
|
97
|
-
prs = db.get_pull_requests()
|
|
98
|
-
assert len(prs) == 3
|
|
99
|
-
|
|
100
|
-
def test_filter_prs_by_state(self, db, sample_prs):
|
|
101
|
-
db.upsert_pull_requests(sample_prs)
|
|
102
|
-
merged = db.get_pull_requests(state="merged")
|
|
103
|
-
assert len(merged) == 2
|
|
104
|
-
|
|
105
|
-
def test_filter_prs_by_author(self, db, sample_prs):
|
|
106
|
-
db.upsert_pull_requests(sample_prs)
|
|
107
|
-
alice = db.get_pull_requests(author="Alice")
|
|
108
|
-
assert len(alice) == 2
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
class TestIssueOperations:
|
|
112
|
-
"""Test issue CRUD operations."""
|
|
113
|
-
|
|
114
|
-
def test_insert_and_retrieve_issues(self, db, sample_issues):
|
|
115
|
-
count = db.upsert_issues(sample_issues)
|
|
116
|
-
assert count == 3
|
|
117
|
-
|
|
118
|
-
issues = db.get_issues()
|
|
119
|
-
assert len(issues) == 3
|
|
120
|
-
|
|
121
|
-
def test_filter_issues_by_state(self, db, sample_issues):
|
|
122
|
-
db.upsert_issues(sample_issues)
|
|
123
|
-
open_issues = db.get_issues(state="open")
|
|
124
|
-
assert len(open_issues) == 2
|
|
125
|
-
|
|
126
|
-
closed_issues = db.get_issues(state="closed")
|
|
127
|
-
assert len(closed_issues) == 1
|
|
128
|
-
|
|
129
|
-
def test_issue_labels_stored_as_json(self, db, sample_issues):
|
|
130
|
-
db.upsert_issues(sample_issues)
|
|
131
|
-
issues = db.get_issues()
|
|
132
|
-
bug_issue = [i for i in issues if i["number"] == 12][0]
|
|
133
|
-
labels = json.loads(bug_issue["labels"])
|
|
134
|
-
assert "bug" in labels
|
|
135
|
-
assert "priority:high" in labels
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
class TestReviewOperations:
|
|
139
|
-
"""Test review CRUD operations."""
|
|
140
|
-
|
|
141
|
-
def test_insert_and_retrieve_reviews(self, db, sample_reviews):
|
|
142
|
-
count = db.upsert_reviews(sample_reviews)
|
|
143
|
-
assert count == 2
|
|
144
|
-
|
|
145
|
-
reviews = db.get_reviews()
|
|
146
|
-
assert len(reviews) == 2
|
|
147
|
-
|
|
148
|
-
def test_filter_reviews_by_author(self, db, sample_reviews):
|
|
149
|
-
db.upsert_reviews(sample_reviews)
|
|
150
|
-
bob_reviews = db.get_reviews(author="Bob")
|
|
151
|
-
assert len(bob_reviews) == 1
|
|
152
|
-
assert bob_reviews[0]["state"] == "APPROVED"
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
class TestGoalOperations:
|
|
156
|
-
"""Test goal CRUD operations."""
|
|
157
|
-
|
|
158
|
-
def test_create_and_list_goals(self, db):
|
|
159
|
-
gid = db.upsert_goal({
|
|
160
|
-
"title": "10 commits/day",
|
|
161
|
-
"target_value": 10,
|
|
162
|
-
"metric": "commits_per_day",
|
|
163
|
-
"deadline": "2026-05-01",
|
|
164
|
-
})
|
|
165
|
-
assert isinstance(gid, int)
|
|
166
|
-
assert gid > 0
|
|
167
|
-
|
|
168
|
-
goals = db.get_goals()
|
|
169
|
-
assert len(goals) == 1
|
|
170
|
-
assert goals[0]["title"] == "10 commits/day"
|
|
171
|
-
|
|
172
|
-
def test_update_goal(self, db):
|
|
173
|
-
gid = db.upsert_goal({
|
|
174
|
-
"title": "10 commits/day",
|
|
175
|
-
"target_value": 10,
|
|
176
|
-
"metric": "commits_per_day",
|
|
177
|
-
})
|
|
178
|
-
db.upsert_goal({
|
|
179
|
-
"id": gid,
|
|
180
|
-
"title": "10 commits/day",
|
|
181
|
-
"target_value": 10,
|
|
182
|
-
"current_value": 5,
|
|
183
|
-
"metric": "commits_per_day",
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
goals = db.get_goals()
|
|
187
|
-
assert goals[0]["current_value"] == 5
|
|
188
|
-
|
|
189
|
-
def test_delete_goal(self, db):
|
|
190
|
-
gid = db.upsert_goal({
|
|
191
|
-
"title": "Test goal",
|
|
192
|
-
"target_value": 5,
|
|
193
|
-
"metric": "test",
|
|
194
|
-
})
|
|
195
|
-
assert db.delete_goal(gid) is True
|
|
196
|
-
assert db.delete_goal(9999) is False # Non-existent
|
|
197
|
-
|
|
198
|
-
def test_filter_goals_by_status(self, db):
|
|
199
|
-
db.upsert_goal({"title": "Active", "target_value": 5, "metric": "test", "status": "active"})
|
|
200
|
-
db.upsert_goal({"title": "Done", "target_value": 5, "metric": "test", "status": "completed"})
|
|
201
|
-
|
|
202
|
-
active = db.get_goals(status="active")
|
|
203
|
-
assert len(active) == 1
|
|
204
|
-
assert active[0]["title"] == "Active"
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
class TestReportCache:
|
|
208
|
-
"""Test report caching."""
|
|
209
|
-
|
|
210
|
-
def test_save_and_get_report(self, db):
|
|
211
|
-
db.save_report("daily", "2026-04-01", "# Daily Report\nContent here")
|
|
212
|
-
content = db.get_report("daily", "2026-04-01")
|
|
213
|
-
assert content is not None
|
|
214
|
-
assert "Daily Report" in content
|
|
215
|
-
|
|
216
|
-
def test_get_nonexistent_report(self, db):
|
|
217
|
-
content = db.get_report("daily", "1999-01-01")
|
|
218
|
-
assert content is None
|
|
219
|
-
|
|
220
|
-
def test_overwrite_report(self, db):
|
|
221
|
-
db.save_report("daily", "2026-04-01", "Version 1")
|
|
222
|
-
db.save_report("daily", "2026-04-01", "Version 2")
|
|
223
|
-
content = db.get_report("daily", "2026-04-01")
|
|
224
|
-
assert "Version 2" in content
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
class TestSprintSnapshots:
|
|
228
|
-
"""Test sprint snapshot operations."""
|
|
229
|
-
|
|
230
|
-
def test_save_and_get_snapshots(self, db):
|
|
231
|
-
db.save_sprint_snapshot({
|
|
232
|
-
"sprint_name": "Sprint 1",
|
|
233
|
-
"total_points": 30,
|
|
234
|
-
"completed_points": 10,
|
|
235
|
-
"remaining_points": 20,
|
|
236
|
-
"added_points": 0,
|
|
237
|
-
})
|
|
238
|
-
snapshots = db.get_sprint_snapshots("Sprint 1")
|
|
239
|
-
assert len(snapshots) == 1
|
|
240
|
-
assert snapshots[0]["total_points"] == 30
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
class TestCodeQuality:
|
|
244
|
-
"""Test code quality snapshot operations."""
|
|
245
|
-
|
|
246
|
-
def test_save_and_get_quality(self, db):
|
|
247
|
-
db.save_quality_snapshot({
|
|
248
|
-
"repo": "testorg/testrepo",
|
|
249
|
-
"test_coverage": 0.85,
|
|
250
|
-
"open_bugs": 3,
|
|
251
|
-
"tech_debt_score": 4.2,
|
|
252
|
-
})
|
|
253
|
-
snapshots = db.get_quality_snapshots(repo="testorg/testrepo")
|
|
254
|
-
assert len(snapshots) == 1
|
|
255
|
-
assert snapshots[0]["test_coverage"] == 0.85
|
|
1
|
+
"""Tests for the Database layer."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from devpulse.core.database import Database
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestDatabaseInit:
|
|
12
|
+
"""Test database initialization."""
|
|
13
|
+
|
|
14
|
+
def test_creates_tables(self, db):
|
|
15
|
+
"""Database should initialize with all required tables."""
|
|
16
|
+
conn = db._connect()
|
|
17
|
+
tables = conn.execute(
|
|
18
|
+
"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
|
|
19
|
+
).fetchall()
|
|
20
|
+
table_names = {t["name"] for t in tables}
|
|
21
|
+
conn.close()
|
|
22
|
+
|
|
23
|
+
expected = {
|
|
24
|
+
"commits", "pull_requests", "issues", "reviews",
|
|
25
|
+
"goals", "reports", "sprint_snapshots", "code_quality",
|
|
26
|
+
}
|
|
27
|
+
assert expected.issubset(table_names), f"Missing tables: {expected - table_names}"
|
|
28
|
+
|
|
29
|
+
def test_idempotent_init(self, db):
|
|
30
|
+
"""Running _init_db twice should not fail."""
|
|
31
|
+
db._init_db()
|
|
32
|
+
db._init_db() # Should not raise
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TestCommitOperations:
|
|
36
|
+
"""Test commit CRUD operations."""
|
|
37
|
+
|
|
38
|
+
def test_insert_and_retrieve_commits(self, db, sample_commits):
|
|
39
|
+
count = db.upsert_commits(sample_commits)
|
|
40
|
+
assert count == 4
|
|
41
|
+
|
|
42
|
+
commits = db.get_commits()
|
|
43
|
+
assert len(commits) == 4
|
|
44
|
+
|
|
45
|
+
def test_upsert_commits_idempotent(self, db, sample_commits):
|
|
46
|
+
db.upsert_commits(sample_commits)
|
|
47
|
+
# Re-upserting same data should not duplicate rows
|
|
48
|
+
db.upsert_commits(sample_commits)
|
|
49
|
+
|
|
50
|
+
commits = db.get_commits()
|
|
51
|
+
assert len(commits) == 4 # No duplicates
|
|
52
|
+
|
|
53
|
+
def test_filter_by_repo(self, db, sample_commits):
|
|
54
|
+
db.upsert_commits(sample_commits)
|
|
55
|
+
commits = db.get_commits(repo="testorg/testrepo")
|
|
56
|
+
assert len(commits) == 4
|
|
57
|
+
|
|
58
|
+
commits = db.get_commits(repo="nonexistent/repo")
|
|
59
|
+
assert len(commits) == 0
|
|
60
|
+
|
|
61
|
+
def test_filter_by_author(self, db, sample_commits):
|
|
62
|
+
db.upsert_commits(sample_commits)
|
|
63
|
+
alice = db.get_commits(author="Alice")
|
|
64
|
+
assert len(alice) == 3
|
|
65
|
+
|
|
66
|
+
bob = db.get_commits(author="Bob")
|
|
67
|
+
assert len(bob) == 1
|
|
68
|
+
|
|
69
|
+
def test_filter_by_date_range(self, db, sample_commits):
|
|
70
|
+
db.upsert_commits(sample_commits)
|
|
71
|
+
commits = db.get_commits(since="2026-04-02", until="2026-04-02")
|
|
72
|
+
assert len(commits) == 1
|
|
73
|
+
assert commits[0]["author"] == "Bob"
|
|
74
|
+
|
|
75
|
+
def test_commit_count_by_day(self, db, sample_commits):
|
|
76
|
+
db.upsert_commits(sample_commits)
|
|
77
|
+
counts = db.get_commit_count_by_day(days=30)
|
|
78
|
+
assert len(counts) == 3 # 3 unique dates
|
|
79
|
+
# April 1 should have 2 commits
|
|
80
|
+
apr1 = [c for c in counts if c["day"] == "2026-04-01"]
|
|
81
|
+
assert len(apr1) == 1
|
|
82
|
+
assert apr1[0]["count"] == 2
|
|
83
|
+
|
|
84
|
+
def test_commit_limit(self, db, sample_commits):
|
|
85
|
+
db.upsert_commits(sample_commits)
|
|
86
|
+
commits = db.get_commits(limit=2)
|
|
87
|
+
assert len(commits) == 2
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class TestPullRequestOperations:
|
|
91
|
+
"""Test pull request CRUD operations."""
|
|
92
|
+
|
|
93
|
+
def test_insert_and_retrieve_prs(self, db, sample_prs):
|
|
94
|
+
count = db.upsert_pull_requests(sample_prs)
|
|
95
|
+
assert count == 3
|
|
96
|
+
|
|
97
|
+
prs = db.get_pull_requests()
|
|
98
|
+
assert len(prs) == 3
|
|
99
|
+
|
|
100
|
+
def test_filter_prs_by_state(self, db, sample_prs):
|
|
101
|
+
db.upsert_pull_requests(sample_prs)
|
|
102
|
+
merged = db.get_pull_requests(state="merged")
|
|
103
|
+
assert len(merged) == 2
|
|
104
|
+
|
|
105
|
+
def test_filter_prs_by_author(self, db, sample_prs):
|
|
106
|
+
db.upsert_pull_requests(sample_prs)
|
|
107
|
+
alice = db.get_pull_requests(author="Alice")
|
|
108
|
+
assert len(alice) == 2
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class TestIssueOperations:
|
|
112
|
+
"""Test issue CRUD operations."""
|
|
113
|
+
|
|
114
|
+
def test_insert_and_retrieve_issues(self, db, sample_issues):
|
|
115
|
+
count = db.upsert_issues(sample_issues)
|
|
116
|
+
assert count == 3
|
|
117
|
+
|
|
118
|
+
issues = db.get_issues()
|
|
119
|
+
assert len(issues) == 3
|
|
120
|
+
|
|
121
|
+
def test_filter_issues_by_state(self, db, sample_issues):
|
|
122
|
+
db.upsert_issues(sample_issues)
|
|
123
|
+
open_issues = db.get_issues(state="open")
|
|
124
|
+
assert len(open_issues) == 2
|
|
125
|
+
|
|
126
|
+
closed_issues = db.get_issues(state="closed")
|
|
127
|
+
assert len(closed_issues) == 1
|
|
128
|
+
|
|
129
|
+
def test_issue_labels_stored_as_json(self, db, sample_issues):
|
|
130
|
+
db.upsert_issues(sample_issues)
|
|
131
|
+
issues = db.get_issues()
|
|
132
|
+
bug_issue = [i for i in issues if i["number"] == 12][0]
|
|
133
|
+
labels = json.loads(bug_issue["labels"])
|
|
134
|
+
assert "bug" in labels
|
|
135
|
+
assert "priority:high" in labels
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class TestReviewOperations:
|
|
139
|
+
"""Test review CRUD operations."""
|
|
140
|
+
|
|
141
|
+
def test_insert_and_retrieve_reviews(self, db, sample_reviews):
|
|
142
|
+
count = db.upsert_reviews(sample_reviews)
|
|
143
|
+
assert count == 2
|
|
144
|
+
|
|
145
|
+
reviews = db.get_reviews()
|
|
146
|
+
assert len(reviews) == 2
|
|
147
|
+
|
|
148
|
+
def test_filter_reviews_by_author(self, db, sample_reviews):
|
|
149
|
+
db.upsert_reviews(sample_reviews)
|
|
150
|
+
bob_reviews = db.get_reviews(author="Bob")
|
|
151
|
+
assert len(bob_reviews) == 1
|
|
152
|
+
assert bob_reviews[0]["state"] == "APPROVED"
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class TestGoalOperations:
|
|
156
|
+
"""Test goal CRUD operations."""
|
|
157
|
+
|
|
158
|
+
def test_create_and_list_goals(self, db):
|
|
159
|
+
gid = db.upsert_goal({
|
|
160
|
+
"title": "10 commits/day",
|
|
161
|
+
"target_value": 10,
|
|
162
|
+
"metric": "commits_per_day",
|
|
163
|
+
"deadline": "2026-05-01",
|
|
164
|
+
})
|
|
165
|
+
assert isinstance(gid, int)
|
|
166
|
+
assert gid > 0
|
|
167
|
+
|
|
168
|
+
goals = db.get_goals()
|
|
169
|
+
assert len(goals) == 1
|
|
170
|
+
assert goals[0]["title"] == "10 commits/day"
|
|
171
|
+
|
|
172
|
+
def test_update_goal(self, db):
|
|
173
|
+
gid = db.upsert_goal({
|
|
174
|
+
"title": "10 commits/day",
|
|
175
|
+
"target_value": 10,
|
|
176
|
+
"metric": "commits_per_day",
|
|
177
|
+
})
|
|
178
|
+
db.upsert_goal({
|
|
179
|
+
"id": gid,
|
|
180
|
+
"title": "10 commits/day",
|
|
181
|
+
"target_value": 10,
|
|
182
|
+
"current_value": 5,
|
|
183
|
+
"metric": "commits_per_day",
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
goals = db.get_goals()
|
|
187
|
+
assert goals[0]["current_value"] == 5
|
|
188
|
+
|
|
189
|
+
def test_delete_goal(self, db):
|
|
190
|
+
gid = db.upsert_goal({
|
|
191
|
+
"title": "Test goal",
|
|
192
|
+
"target_value": 5,
|
|
193
|
+
"metric": "test",
|
|
194
|
+
})
|
|
195
|
+
assert db.delete_goal(gid) is True
|
|
196
|
+
assert db.delete_goal(9999) is False # Non-existent
|
|
197
|
+
|
|
198
|
+
def test_filter_goals_by_status(self, db):
|
|
199
|
+
db.upsert_goal({"title": "Active", "target_value": 5, "metric": "test", "status": "active"})
|
|
200
|
+
db.upsert_goal({"title": "Done", "target_value": 5, "metric": "test", "status": "completed"})
|
|
201
|
+
|
|
202
|
+
active = db.get_goals(status="active")
|
|
203
|
+
assert len(active) == 1
|
|
204
|
+
assert active[0]["title"] == "Active"
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class TestReportCache:
|
|
208
|
+
"""Test report caching."""
|
|
209
|
+
|
|
210
|
+
def test_save_and_get_report(self, db):
|
|
211
|
+
db.save_report("daily", "2026-04-01", "# Daily Report\nContent here")
|
|
212
|
+
content = db.get_report("daily", "2026-04-01")
|
|
213
|
+
assert content is not None
|
|
214
|
+
assert "Daily Report" in content
|
|
215
|
+
|
|
216
|
+
def test_get_nonexistent_report(self, db):
|
|
217
|
+
content = db.get_report("daily", "1999-01-01")
|
|
218
|
+
assert content is None
|
|
219
|
+
|
|
220
|
+
def test_overwrite_report(self, db):
|
|
221
|
+
db.save_report("daily", "2026-04-01", "Version 1")
|
|
222
|
+
db.save_report("daily", "2026-04-01", "Version 2")
|
|
223
|
+
content = db.get_report("daily", "2026-04-01")
|
|
224
|
+
assert "Version 2" in content
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class TestSprintSnapshots:
|
|
228
|
+
"""Test sprint snapshot operations."""
|
|
229
|
+
|
|
230
|
+
def test_save_and_get_snapshots(self, db):
|
|
231
|
+
db.save_sprint_snapshot({
|
|
232
|
+
"sprint_name": "Sprint 1",
|
|
233
|
+
"total_points": 30,
|
|
234
|
+
"completed_points": 10,
|
|
235
|
+
"remaining_points": 20,
|
|
236
|
+
"added_points": 0,
|
|
237
|
+
})
|
|
238
|
+
snapshots = db.get_sprint_snapshots("Sprint 1")
|
|
239
|
+
assert len(snapshots) == 1
|
|
240
|
+
assert snapshots[0]["total_points"] == 30
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class TestCodeQuality:
|
|
244
|
+
"""Test code quality snapshot operations."""
|
|
245
|
+
|
|
246
|
+
def test_save_and_get_quality(self, db):
|
|
247
|
+
db.save_quality_snapshot({
|
|
248
|
+
"repo": "testorg/testrepo",
|
|
249
|
+
"test_coverage": 0.85,
|
|
250
|
+
"open_bugs": 3,
|
|
251
|
+
"tech_debt_score": 4.2,
|
|
252
|
+
})
|
|
253
|
+
snapshots = db.get_quality_snapshots(repo="testorg/testrepo")
|
|
254
|
+
assert len(snapshots) == 1
|
|
255
|
+
assert snapshots[0]["test_coverage"] == 0.85
|