forked from pacta-dev/pacta-cli
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_github_renderer.py
More file actions
294 lines (260 loc) · 10.3 KB
/
Copy pathtest_github_renderer.py
File metadata and controls
294 lines (260 loc) · 10.3 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
from pacta import PACTA_VERSION
from pacta.reporting.renderers.github import GitHubReportRenderer
from pacta.reporting.types import (
DiffSummary,
Report,
ReportLocation,
RuleRef,
RunInfo,
Severity,
Summary,
TrendPoint,
TrendSummary,
Violation,
)
def _make_run(**overrides):
defaults = dict(
repo_root="/tmp/repo",
commit="abc1234def",
branch="feature/billing",
model_file="architecture.yml",
rules_files=("rules.pacta.yml",),
baseline_ref="baseline",
mode="full",
created_at="2025-01-22T12:00:00+00:00",
tool_version=PACTA_VERSION,
metadata={},
)
defaults.update(overrides)
return RunInfo(**defaults)
def _make_violation(
*,
rule_id="no-domain-infra",
name="Domain cannot depend on Infra",
severity=Severity.ERROR,
status="new",
file="src/domain/service.py",
line=12,
suggestion="Use dependency injection",
):
return Violation(
rule=RuleRef(id=rule_id, name=name, severity=severity),
message="Forbidden dependency",
status=status,
location=ReportLocation(file=file, line=line, column=1),
context={
"target": "dependency",
"dep_type": "import",
"src_fqname": "app.domain.BillingService",
"dst_fqname": "app.infra.PostgresClient",
"src_layer": "domain",
"dst_layer": "infra",
},
violation_key="key1",
suggestion=suggestion,
)
def _make_report(*, violations=(), diff=None, trends=None, baseline_ref="baseline"):
all_v = list(violations)
by_sev = {}
by_status = {}
by_rule = {}
for v in all_v:
sev = v.rule.severity.value
by_sev[sev] = by_sev.get(sev, 0) + 1
by_status[v.status] = by_status.get(v.status, 0) + 1
by_rule[v.rule.id] = by_rule.get(v.rule.id, 0) + 1
return Report(
tool="pacta",
version=PACTA_VERSION,
run=_make_run(baseline_ref=baseline_ref),
summary=Summary(
total_violations=len(all_v),
by_severity=by_sev,
by_status=by_status,
by_rule=by_rule,
engine_errors=0,
),
violations=tuple(all_v),
engine_errors=(),
diff=diff,
trends=trends,
)
class TestGitHubRendererHeader:
def test_header_includes_branch_and_commit(self):
report = _make_report()
out = GitHubReportRenderer().render(report)
assert "## Architecture Report" in out
assert "`feature/billing`" in out
assert "`abc1234`" in out
assert "`baseline`" in out
def test_no_branch_or_commit(self):
report = Report(
tool="pacta",
version=PACTA_VERSION,
run=_make_run(branch=None, commit=None, baseline_ref=None),
summary=Summary(total_violations=0, by_severity={}, by_status={}, by_rule={}, engine_errors=0),
violations=(),
engine_errors=(),
)
out = GitHubReportRenderer().render(report)
assert "## Architecture Report" in out
assert "Branch" not in out
class TestGitHubRendererStructuralChanges:
def test_diff_table_rendered(self):
diff = DiffSummary(
nodes_added=3,
nodes_removed=1,
edges_added=5,
edges_removed=2,
added_node_names=("app.billing.InvoiceService", "app.billing.TaxCalc"),
removed_node_names=("app.legacy.OldBilling",),
)
report = _make_report(diff=diff)
out = GitHubReportRenderer().render(report)
assert "### Structural Changes" in out
assert "+3" in out
assert "-1" in out
assert "`app.billing.InvoiceService`" in out
assert "**Removed modules:**" in out
def test_no_diff_section_when_empty(self):
report = _make_report()
out = GitHubReportRenderer().render(report)
assert "### Structural Changes" not in out
class TestGitHubRendererViolations:
def test_new_violations_with_baseline(self):
v = _make_violation(status="new")
report = _make_report(violations=[v], baseline_ref="baseline")
out = GitHubReportRenderer().render(report)
assert "### New Violations (action required)" in out
assert "`no-domain-infra`" in out
assert "src/domain/service.py:12" in out
assert "Use dependency injection" in out
def test_all_violations_shown_without_baseline(self):
v = _make_violation(status="unknown")
report = _make_report(violations=[v], baseline_ref=None)
out = GitHubReportRenderer().render(report)
assert "### Violation Details" in out
assert "`no-domain-infra`" in out
assert "src/domain/service.py:12" in out
def test_severity_summary_without_baseline(self):
v1 = _make_violation(status="unknown", severity=Severity.WARNING)
v2 = _make_violation(status="unknown", severity=Severity.INFO, rule_id="other", name="Other")
report = _make_report(violations=[v1, v2], baseline_ref=None)
out = GitHubReportRenderer().render(report)
assert "### Violations (2 total)" in out
assert "Warning" in out
assert "Info" in out
def test_status_summary_with_baseline(self):
new_v = _make_violation(status="new")
existing_v = _make_violation(status="existing", rule_id="other-rule", name="Other")
report = _make_report(violations=[new_v, existing_v], baseline_ref="baseline")
out = GitHubReportRenderer().render(report)
assert "### Violations Summary" in out
assert "New" in out
assert "Existing" in out
def test_existing_violations_collapsible(self):
v = _make_violation(status="existing")
report = _make_report(violations=[v], baseline_ref="baseline")
out = GitHubReportRenderer().render(report)
assert "<details>" in out
assert "Existing Violations" in out
def test_fixed_violations_section(self):
v = _make_violation(status="fixed", rule_id="no-circular", name="No circular deps")
report = _make_report(violations=[v])
out = GitHubReportRenderer().render(report)
assert "### Fixed Violations" in out
assert "~~`no-circular`" in out
assert "(resolved)" in out
def test_no_violations_no_summary(self):
report = _make_report()
out = GitHubReportRenderer().render(report)
assert "### Violations" not in out
class TestGitHubRendererTrends:
def test_trends_table_rendered(self):
trends = TrendSummary(
points=(
TrendPoint(label="Jan 20", violations=5, nodes=40, edges=67, density=1.68),
TrendPoint(label="Jan 22", violations=3, nodes=42, edges=67, density=1.60),
),
violation_change=-2,
node_change=2,
edge_change=0,
density_change=-0.08,
)
report = _make_report(trends=trends)
out = GitHubReportRenderer().render(report)
assert "### Architecture Trends (last 2 snapshots)" in out
assert "Violations" in out
assert "Improving" in out
assert "-2" in out
def test_no_trends_when_none(self):
report = _make_report()
out = GitHubReportRenderer().render(report)
assert "### Architecture Trends" not in out
class TestGitHubRendererTableAlignment:
def test_tables_have_aligned_columns(self):
trends = TrendSummary(
points=(TrendPoint(label="Jan 22", violations=3, nodes=42, edges=67, density=1.60),),
violation_change=0,
node_change=0,
edge_change=0,
density_change=0,
)
report = _make_report(trends=trends)
out = GitHubReportRenderer().render(report)
# All rows in a table should have the same length
for line in out.split("\n"):
if line.startswith("|") and "---" not in line:
# Each cell should be padded
assert " " in line, f"Table row not padded: {line}"
class TestGitHubRendererFooter:
def test_footer_includes_version(self):
report = _make_report()
out = GitHubReportRenderer().render(report)
assert "Pacta" in out
assert PACTA_VERSION in out
class TestGitHubRendererFullOutput:
def test_full_report_with_baseline(self):
diff = DiffSummary(
nodes_added=3,
nodes_removed=1,
edges_added=5,
edges_removed=2,
added_node_names=("app.billing.InvoiceService",),
removed_node_names=("app.legacy.OldBilling",),
)
trends = TrendSummary(
points=(
TrendPoint(label="Jan 20", violations=5, nodes=40, edges=67, density=1.68),
TrendPoint(label="Jan 22", violations=3, nodes=42, edges=67, density=1.60),
),
violation_change=-2,
node_change=2,
edge_change=0,
density_change=-0.08,
)
new_v = _make_violation(status="new")
fixed_v = _make_violation(status="fixed", rule_id="no-circular", name="No circular deps")
report = _make_report(violations=[new_v, fixed_v], diff=diff, trends=trends)
out = GitHubReportRenderer().render(report)
assert "## Architecture Report" in out
assert "### Structural Changes" in out
assert "### Violations Summary" in out
assert "### New Violations" in out
assert "### Fixed Violations" in out
assert "### Architecture Trends" in out
assert "Generated by" in out
def test_full_report_without_baseline(self):
v1 = _make_violation(status="unknown", severity=Severity.WARNING)
v2 = _make_violation(status="unknown", severity=Severity.INFO, rule_id="other", name="Other")
report = _make_report(violations=[v1, v2], baseline_ref=None)
out = GitHubReportRenderer().render(report)
assert "## Architecture Report" in out
assert "### Violations (2 total)" in out
assert "### Violation Details" in out
assert "`no-domain-infra`" in out
assert "`other`" in out
assert "Generated by" in out
# No baseline-specific sections
assert "### Violations Summary" not in out
assert "### New Violations" not in out