@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.
Files changed (40) hide show
  1. package/.editorconfig +12 -0
  2. package/.github/ISSUE_TEMPLATE/bug_report.yml +43 -0
  3. package/.github/ISSUE_TEMPLATE/feature_request.yml +33 -0
  4. package/.github/PULL_REQUEST_TEMPLATE.md +18 -0
  5. package/.github/dependabot.yml +16 -0
  6. package/.github/workflows/ci.yml +33 -0
  7. package/CODE_OF_CONDUCT.md +27 -0
  8. package/Dockerfile +8 -0
  9. package/LICENSE +21 -21
  10. package/README.md +135 -39
  11. package/SECURITY.md +22 -0
  12. package/devpulse/__init__.py +4 -4
  13. package/devpulse/api/__init__.py +1 -1
  14. package/devpulse/api/app.py +371 -371
  15. package/devpulse/cli/__init__.py +1 -1
  16. package/devpulse/cli/dashboard.py +131 -131
  17. package/devpulse/cli/main.py +678 -678
  18. package/devpulse/cli/render.py +175 -175
  19. package/devpulse/core/__init__.py +34 -34
  20. package/devpulse/core/analytics.py +487 -487
  21. package/devpulse/core/config.py +77 -77
  22. package/devpulse/core/database.py +612 -612
  23. package/devpulse/core/github_client.py +281 -281
  24. package/devpulse/core/models.py +142 -142
  25. package/devpulse/core/report_generator.py +454 -454
  26. package/devpulse/static/.gitkeep +1 -1
  27. package/devpulse/templates/report.html +64 -64
  28. package/package.json +35 -35
  29. package/pyproject.toml +80 -80
  30. package/requirements.txt +14 -14
  31. package/tests/__init__.py +1 -1
  32. package/tests/conftest.py +208 -208
  33. package/tests/test_analytics.py +284 -284
  34. package/tests/test_api.py +313 -313
  35. package/tests/test_cli.py +204 -204
  36. package/tests/test_config.py +47 -47
  37. package/tests/test_database.py +255 -255
  38. package/tests/test_models.py +107 -107
  39. package/tests/test_report_generator.py +173 -173
  40. package/jest.config.js +0 -7
package/tests/test_cli.py CHANGED
@@ -1,204 +1,204 @@
1
- """Tests for CLI commands."""
2
-
3
- import json
4
- from unittest.mock import patch, MagicMock
5
-
6
- import pytest
7
- from click.testing import CliRunner
8
-
9
- from devpulse.cli.main import cli
10
-
11
-
12
- @pytest.fixture
13
- def runner():
14
- return CliRunner()
15
-
16
-
17
- class TestCLIBase:
18
- """Test CLI base commands."""
19
-
20
- def test_version(self, runner):
21
- result = runner.invoke(cli, ["--version"])
22
- assert result.exit_code == 0
23
- assert "1.0.0" in result.output
24
-
25
- def test_help(self, runner):
26
- result = runner.invoke(cli, ["--help"])
27
- assert result.exit_code == 0
28
- assert "DevPulse" in result.output
29
- assert "sync" in result.output
30
- assert "daily" in result.output
31
- assert "metrics" in result.output
32
-
33
-
34
- class TestSyncCommand:
35
- """Test sync command (without actual GitHub)."""
36
-
37
- def test_sync_no_token(self, runner, monkeypatch):
38
- monkeypatch.delenv("DEVPULSE_GITHUB_TOKEN", raising=False)
39
- monkeypatch.setenv("DEVPULSE_GITHUB_TOKEN", "")
40
- from devpulse.core.config import reset_settings
41
- reset_settings()
42
-
43
- result = runner.invoke(cli, ["sync"])
44
- assert result.exit_code != 0 or "DEVPULSE_GITHUB_TOKEN" in result.output
45
-
46
-
47
- class TestReportCommands:
48
- """Test report generation CLI commands."""
49
-
50
- def test_daily_report_terminal(self, runner, populated_db):
51
- with patch("devpulse.core.report_generator.ReportGenerator", return_value=MagicMock(
52
- daily_report=MagicMock(return_value={
53
- "date": "2026-04-01",
54
- "author": "all",
55
- "commits": 2,
56
- "prs_opened": 0,
57
- "prs_merged": 0,
58
- "issues_opened": 0,
59
- "issues_closed": 0,
60
- "reviews_given": 0,
61
- "lines_changed": 80,
62
- "summary": "2 commit(s)",
63
- "commit_details": [],
64
- }),
65
- )):
66
- result = runner.invoke(cli, ["daily", "--date", "2026-04-01"])
67
- # Should not crash (may print to console)
68
-
69
- def test_daily_report_json(self, runner, populated_db):
70
- with patch("devpulse.core.report_generator.ReportGenerator", return_value=MagicMock(
71
- daily_report=MagicMock(return_value={
72
- "date": "2026-04-01",
73
- "author": "all",
74
- "commits": 2,
75
- "prs_opened": 0,
76
- "prs_merged": 0,
77
- "issues_opened": 0,
78
- "issues_closed": 0,
79
- "reviews_given": 0,
80
- "lines_changed": 80,
81
- "summary": "2 commits",
82
- "commit_details": [],
83
- }),
84
- to_json=MagicMock(return_value='{"date": "2026-04-01"}'),
85
- )):
86
- result = runner.invoke(cli, ["daily", "--format", "json"])
87
- # Should output JSON
88
-
89
- def test_weekly_report_help(self, runner):
90
- result = runner.invoke(cli, ["weekly", "--help"])
91
- assert result.exit_code == 0
92
- assert "--week-start" in result.output
93
-
94
- def test_monthly_report_help(self, runner):
95
- result = runner.invoke(cli, ["monthly", "--help"])
96
- assert result.exit_code == 0
97
- assert "--year" in result.output
98
-
99
-
100
- class TestMetricsCommand:
101
- """Test metrics CLI commands."""
102
-
103
- def test_metrics_help(self, runner):
104
- result = runner.invoke(cli, ["metrics", "--help"])
105
- assert result.exit_code == 0
106
- assert "--author" in result.output
107
- assert "--days" in result.output
108
-
109
-
110
- class TestInsightsCommand:
111
- """Test insights CLI command."""
112
-
113
- def test_insights_help(self, runner):
114
- result = runner.invoke(cli, ["insights", "--help"])
115
- assert result.exit_code == 0
116
- assert "--days" in result.output
117
-
118
-
119
- class TestHealthCommand:
120
- """Test health CLI command."""
121
-
122
- def test_health_help(self, runner):
123
- result = runner.invoke(cli, ["health", "--help"])
124
- assert result.exit_code == 0
125
- assert "--days" in result.output
126
-
127
-
128
- class TestHeatmapCommand:
129
- """Test heatmap CLI command."""
130
-
131
- def test_heatmap_help(self, runner):
132
- result = runner.invoke(cli, ["heatmap", "--help"])
133
- assert result.exit_code == 0
134
- assert "--author" in result.output
135
- assert "--days" in result.output
136
-
137
-
138
- class TestGoalsCommands:
139
- """Test goals CLI commands."""
140
-
141
- def test_goals_help(self, runner):
142
- result = runner.invoke(cli, ["goals", "--help"])
143
- assert result.exit_code == 0
144
-
145
- def test_goals_add_help(self, runner):
146
- result = runner.invoke(cli, ["goals", "add", "--help"])
147
- assert result.exit_code == 0
148
- assert "--title" in result.output
149
-
150
- def test_goals_list_help(self, runner):
151
- result = runner.invoke(cli, ["goals", "list", "--help"])
152
- assert result.exit_code == 0
153
-
154
- def test_goals_coach_help(self, runner):
155
- result = runner.invoke(cli, ["goals", "coach", "--help"])
156
- assert result.exit_code == 0
157
-
158
-
159
- class TestSprintCommands:
160
- """Test sprint CLI commands."""
161
-
162
- def test_sprint_help(self, runner):
163
- result = runner.invoke(cli, ["sprint", "--help"])
164
- assert result.exit_code == 0
165
-
166
- def test_sprint_snapshot_help(self, runner):
167
- result = runner.invoke(cli, ["sprint", "snapshot", "--help"])
168
- assert result.exit_code == 0
169
- assert "--name" in result.output
170
-
171
- def test_sprint_burndown_help(self, runner):
172
- result = runner.invoke(cli, ["sprint", "burndown", "--help"])
173
- assert result.exit_code == 0
174
-
175
- def test_sprint_predict_help(self, runner):
176
- result = runner.invoke(cli, ["sprint", "predict", "--help"])
177
- assert result.exit_code == 0
178
-
179
-
180
- class TestQualityCommand:
181
- """Test quality CLI command."""
182
-
183
- def test_quality_help(self, runner):
184
- result = runner.invoke(cli, ["quality", "--help"])
185
- assert result.exit_code == 0
186
- assert "--repo" in result.output
187
-
188
-
189
- class TestServeCommand:
190
- """Test serve CLI command."""
191
-
192
- def test_serve_help(self, runner):
193
- result = runner.invoke(cli, ["serve", "--help"])
194
- assert result.exit_code == 0
195
- assert "--port" in result.output
196
-
197
-
198
- class TestDashboardCommand:
199
- """Test dashboard CLI command."""
200
-
201
- def test_dashboard_help(self, runner):
202
- result = runner.invoke(cli, ["dashboard", "--help"])
203
- assert result.exit_code == 0
204
- assert "--author" in result.output
1
+ """Tests for CLI commands."""
2
+
3
+ import json
4
+ from unittest.mock import patch, MagicMock
5
+
6
+ import pytest
7
+ from click.testing import CliRunner
8
+
9
+ from devpulse.cli.main import cli
10
+
11
+
12
+ @pytest.fixture
13
+ def runner():
14
+ return CliRunner()
15
+
16
+
17
+ class TestCLIBase:
18
+ """Test CLI base commands."""
19
+
20
+ def test_version(self, runner):
21
+ result = runner.invoke(cli, ["--version"])
22
+ assert result.exit_code == 0
23
+ assert "1.0.0" in result.output
24
+
25
+ def test_help(self, runner):
26
+ result = runner.invoke(cli, ["--help"])
27
+ assert result.exit_code == 0
28
+ assert "DevPulse" in result.output
29
+ assert "sync" in result.output
30
+ assert "daily" in result.output
31
+ assert "metrics" in result.output
32
+
33
+
34
+ class TestSyncCommand:
35
+ """Test sync command (without actual GitHub)."""
36
+
37
+ def test_sync_no_token(self, runner, monkeypatch):
38
+ monkeypatch.delenv("DEVPULSE_GITHUB_TOKEN", raising=False)
39
+ monkeypatch.setenv("DEVPULSE_GITHUB_TOKEN", "")
40
+ from devpulse.core.config import reset_settings
41
+ reset_settings()
42
+
43
+ result = runner.invoke(cli, ["sync"])
44
+ assert result.exit_code != 0 or "DEVPULSE_GITHUB_TOKEN" in result.output
45
+
46
+
47
+ class TestReportCommands:
48
+ """Test report generation CLI commands."""
49
+
50
+ def test_daily_report_terminal(self, runner, populated_db):
51
+ with patch("devpulse.core.report_generator.ReportGenerator", return_value=MagicMock(
52
+ daily_report=MagicMock(return_value={
53
+ "date": "2026-04-01",
54
+ "author": "all",
55
+ "commits": 2,
56
+ "prs_opened": 0,
57
+ "prs_merged": 0,
58
+ "issues_opened": 0,
59
+ "issues_closed": 0,
60
+ "reviews_given": 0,
61
+ "lines_changed": 80,
62
+ "summary": "2 commit(s)",
63
+ "commit_details": [],
64
+ }),
65
+ )):
66
+ result = runner.invoke(cli, ["daily", "--date", "2026-04-01"])
67
+ # Should not crash (may print to console)
68
+
69
+ def test_daily_report_json(self, runner, populated_db):
70
+ with patch("devpulse.core.report_generator.ReportGenerator", return_value=MagicMock(
71
+ daily_report=MagicMock(return_value={
72
+ "date": "2026-04-01",
73
+ "author": "all",
74
+ "commits": 2,
75
+ "prs_opened": 0,
76
+ "prs_merged": 0,
77
+ "issues_opened": 0,
78
+ "issues_closed": 0,
79
+ "reviews_given": 0,
80
+ "lines_changed": 80,
81
+ "summary": "2 commits",
82
+ "commit_details": [],
83
+ }),
84
+ to_json=MagicMock(return_value='{"date": "2026-04-01"}'),
85
+ )):
86
+ result = runner.invoke(cli, ["daily", "--format", "json"])
87
+ # Should output JSON
88
+
89
+ def test_weekly_report_help(self, runner):
90
+ result = runner.invoke(cli, ["weekly", "--help"])
91
+ assert result.exit_code == 0
92
+ assert "--week-start" in result.output
93
+
94
+ def test_monthly_report_help(self, runner):
95
+ result = runner.invoke(cli, ["monthly", "--help"])
96
+ assert result.exit_code == 0
97
+ assert "--year" in result.output
98
+
99
+
100
+ class TestMetricsCommand:
101
+ """Test metrics CLI commands."""
102
+
103
+ def test_metrics_help(self, runner):
104
+ result = runner.invoke(cli, ["metrics", "--help"])
105
+ assert result.exit_code == 0
106
+ assert "--author" in result.output
107
+ assert "--days" in result.output
108
+
109
+
110
+ class TestInsightsCommand:
111
+ """Test insights CLI command."""
112
+
113
+ def test_insights_help(self, runner):
114
+ result = runner.invoke(cli, ["insights", "--help"])
115
+ assert result.exit_code == 0
116
+ assert "--days" in result.output
117
+
118
+
119
+ class TestHealthCommand:
120
+ """Test health CLI command."""
121
+
122
+ def test_health_help(self, runner):
123
+ result = runner.invoke(cli, ["health", "--help"])
124
+ assert result.exit_code == 0
125
+ assert "--days" in result.output
126
+
127
+
128
+ class TestHeatmapCommand:
129
+ """Test heatmap CLI command."""
130
+
131
+ def test_heatmap_help(self, runner):
132
+ result = runner.invoke(cli, ["heatmap", "--help"])
133
+ assert result.exit_code == 0
134
+ assert "--author" in result.output
135
+ assert "--days" in result.output
136
+
137
+
138
+ class TestGoalsCommands:
139
+ """Test goals CLI commands."""
140
+
141
+ def test_goals_help(self, runner):
142
+ result = runner.invoke(cli, ["goals", "--help"])
143
+ assert result.exit_code == 0
144
+
145
+ def test_goals_add_help(self, runner):
146
+ result = runner.invoke(cli, ["goals", "add", "--help"])
147
+ assert result.exit_code == 0
148
+ assert "--title" in result.output
149
+
150
+ def test_goals_list_help(self, runner):
151
+ result = runner.invoke(cli, ["goals", "list", "--help"])
152
+ assert result.exit_code == 0
153
+
154
+ def test_goals_coach_help(self, runner):
155
+ result = runner.invoke(cli, ["goals", "coach", "--help"])
156
+ assert result.exit_code == 0
157
+
158
+
159
+ class TestSprintCommands:
160
+ """Test sprint CLI commands."""
161
+
162
+ def test_sprint_help(self, runner):
163
+ result = runner.invoke(cli, ["sprint", "--help"])
164
+ assert result.exit_code == 0
165
+
166
+ def test_sprint_snapshot_help(self, runner):
167
+ result = runner.invoke(cli, ["sprint", "snapshot", "--help"])
168
+ assert result.exit_code == 0
169
+ assert "--name" in result.output
170
+
171
+ def test_sprint_burndown_help(self, runner):
172
+ result = runner.invoke(cli, ["sprint", "burndown", "--help"])
173
+ assert result.exit_code == 0
174
+
175
+ def test_sprint_predict_help(self, runner):
176
+ result = runner.invoke(cli, ["sprint", "predict", "--help"])
177
+ assert result.exit_code == 0
178
+
179
+
180
+ class TestQualityCommand:
181
+ """Test quality CLI command."""
182
+
183
+ def test_quality_help(self, runner):
184
+ result = runner.invoke(cli, ["quality", "--help"])
185
+ assert result.exit_code == 0
186
+ assert "--repo" in result.output
187
+
188
+
189
+ class TestServeCommand:
190
+ """Test serve CLI command."""
191
+
192
+ def test_serve_help(self, runner):
193
+ result = runner.invoke(cli, ["serve", "--help"])
194
+ assert result.exit_code == 0
195
+ assert "--port" in result.output
196
+
197
+
198
+ class TestDashboardCommand:
199
+ """Test dashboard CLI command."""
200
+
201
+ def test_dashboard_help(self, runner):
202
+ result = runner.invoke(cli, ["dashboard", "--help"])
203
+ assert result.exit_code == 0
204
+ assert "--author" in result.output
@@ -1,47 +1,47 @@
1
- """Tests for configuration management."""
2
-
3
- import os
4
- import pytest
5
-
6
- from devpulse.core.config import Settings, get_settings, reset_settings
7
-
8
-
9
- class TestSettings:
10
- """Test settings loading."""
11
-
12
- def test_default_settings(self):
13
- reset_settings()
14
- # Create Settings directly to check defaults (env vars are set by conftest)
15
- s = Settings(github_token="", github_username="")
16
- assert s.api_port == 8742
17
- assert s.log_level == "INFO"
18
- assert s.redact_sensitive is True
19
-
20
- def test_env_override(self, monkeypatch):
21
- reset_settings()
22
- monkeypatch.setenv("DEVPULSE_GITHUB_TOKEN", "ghp_test123")
23
- monkeypatch.setenv("DEVPULSE_API_PORT", "9999")
24
- settings = Settings()
25
- assert settings.github_token == "ghp_test123"
26
- assert settings.api_port == 9999
27
-
28
- def test_get_settings_caches(self):
29
- reset_settings()
30
- s1 = get_settings()
31
- s2 = get_settings()
32
- assert s1 is s2
33
-
34
- def test_reset_settings(self):
35
- reset_settings()
36
- s1 = get_settings()
37
- reset_settings()
38
- s2 = get_settings()
39
- assert s1 is not s2
40
-
41
- def test_database_path_default(self):
42
- settings = Settings()
43
- assert "devpulse.db" in settings.database_path
44
-
45
- def test_burnout_threshold(self):
46
- settings = Settings()
47
- assert 0 < settings.burnout_threshold < 1
1
+ """Tests for configuration management."""
2
+
3
+ import os
4
+ import pytest
5
+
6
+ from devpulse.core.config import Settings, get_settings, reset_settings
7
+
8
+
9
+ class TestSettings:
10
+ """Test settings loading."""
11
+
12
+ def test_default_settings(self):
13
+ reset_settings()
14
+ # Create Settings directly to check defaults (env vars are set by conftest)
15
+ s = Settings(github_token="", github_username="")
16
+ assert s.api_port == 8742
17
+ assert s.log_level == "INFO"
18
+ assert s.redact_sensitive is True
19
+
20
+ def test_env_override(self, monkeypatch):
21
+ reset_settings()
22
+ monkeypatch.setenv("DEVPULSE_GITHUB_TOKEN", "ghp_test123")
23
+ monkeypatch.setenv("DEVPULSE_API_PORT", "9999")
24
+ settings = Settings()
25
+ assert settings.github_token == "ghp_test123"
26
+ assert settings.api_port == 9999
27
+
28
+ def test_get_settings_caches(self):
29
+ reset_settings()
30
+ s1 = get_settings()
31
+ s2 = get_settings()
32
+ assert s1 is s2
33
+
34
+ def test_reset_settings(self):
35
+ reset_settings()
36
+ s1 = get_settings()
37
+ reset_settings()
38
+ s2 = get_settings()
39
+ assert s1 is not s2
40
+
41
+ def test_database_path_default(self):
42
+ settings = Settings()
43
+ assert "devpulse.db" in settings.database_path
44
+
45
+ def test_burnout_threshold(self):
46
+ settings = Settings()
47
+ assert 0 < settings.burnout_threshold < 1