Repository navigation
Expand file tree
/
Copy pathtest_projects.py
More file actions
264 lines (222 loc) · 9.44 KB
/
Copy pathtest_projects.py
File metadata and controls
264 lines (222 loc) · 9.44 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
import re
import shutil
from pathlib import Path
from xml.dom import minidom
import pytest
from PIL import Image
from brand.build import geometry as g
from brand.build import tokens as t
from brand.build import projects as p
from brand.build.raster import export_png
def test_project_frame_parses_and_uses_tokens() -> None:
frame = g.project_frame(struct=t.GREEN_INK, accent=t.GOLD_LIGHT)
minidom.parseString(
f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">{frame}</svg>'
)
assert t.GREEN_INK in frame and t.GOLD_LIGHT in frame
EXPECTED_REPOS = {
"modern-di",
"that-depends",
"modern-di-fastapi",
"modern-di-litestar",
"modern-di-starlette",
"modern-di-aiohttp",
"modern-di-faststream",
"modern-di-typer",
"modern-di-pytest",
"modern-di-aiogram",
"fastapi-sqlalchemy-template",
"litestar-sqlalchemy-template",
"chat-app",
"lite-bootstrap",
"httpware",
"faststream-redis-timers",
"faststream-concurrent-aiokafka",
"faststream-outbox",
"db-retry",
"eof-fixer",
"semvertag",
"modern-di-arq",
"compose2pod",
"modern-di-celery",
"modern-di-taskiq",
"modern-di-flask",
"modern-di-grpc",
"jwks-client",
"release-scope",
}
def test_manifest_covers_every_repo() -> None:
assert set(p.MANIFEST) == EXPECTED_REPOS
@pytest.mark.parametrize("repo", sorted(EXPECTED_REPOS))
def test_project_mark_is_valid_svg(repo: str) -> None:
svg = p.project_mark(repo)
minidom.parseString(svg)
assert svg.startswith("<svg") and 'viewBox="0 0 100 100"' in svg
@pytest.mark.parametrize("repo", sorted(EXPECTED_REPOS))
def test_only_allowed_colours(repo: str) -> None:
"""INVARIANT: a project mark carries no ink outside the brand palette, and no
per-project accent hue.
Projects are told apart by the shape of the inner symbol, never by colour
(`docs/adr/0001-project-marks-single-gold-inner.md`). A new symbol drawn from a
partner's brand colour breaks it, and the drift is invisible until the marks are
seen side by side.
"""
hexes = {h.lower() for h in re.findall(r"#[0-9a-fA-F]{6}", p.project_mark(repo))}
assert hexes <= p.ALLOWED_COLORS, (
f"{repo} stray colours: {hexes - p.ALLOWED_COLORS}"
)
def test_templates_use_chevron() -> None:
"""INVARIANT: the two project templates reuse the org chevron rather than getting
an inner symbol of their own.
They are not published packages, and giving each one a bespoke symbol is the
natural thing to do when adding the next template — it dilutes the rule that an
inner symbol marks a library.
"""
for repo in ("fastapi-sqlalchemy-template", "litestar-sqlalchemy-template"):
assert "<polyline" in p.project_mark(repo)
def test_render_projects_writes_every_mark(tmp_path: Path) -> None:
written = p.render_projects(out_dir=tmp_path)
assert len(written) == len(EXPECTED_REPOS)
for repo in EXPECTED_REPOS:
svg = tmp_path / repo / "mark.svg"
assert svg.is_file() and svg.read_text(encoding="utf-8").startswith("<svg")
@pytest.mark.parametrize("repo", ["modern-di", "faststream-outbox", "semvertag"])
def test_lockup_is_valid_and_names_repo(repo: str) -> None:
svg = p.project_lockup(repo)
minidom.parseString(svg)
assert svg.startswith("<svg")
def test_render_projects_writes_lockup(tmp_path: Path) -> None:
p.render_projects(out_dir=tmp_path)
repo_dir = tmp_path / "modern-di"
assert (repo_dir / "lockup-light.svg").is_file()
assert (repo_dir / "lockup-dark.svg").is_file()
if shutil.which("rsvg-convert"):
assert (repo_dir / "lockup.png").is_file()
def test_fit_text_shrinks_only_when_needed() -> None:
short_svg, short_size = p.fit_text(
"hi", 74, 700, color="#356852", x=0, baseline_y=0
)
assert short_size == 74 # fits, unchanged
long_svg, long_size = p.fit_text(
"x" * 80, 74, 700, color="#356852", x=0, baseline_y=0
)
assert long_size < 74 # too wide -> shrunk
assert "<g" in short_svg and "<g" in long_svg
def test_wrap_text_splits_long_and_keeps_short() -> None:
assert len(p.wrap_text("short tagline", 30, 700)) == 1
assert len(p.wrap_text("word " * 60, 30, 700)) > 1
DOCS_EXPECTED = {
"modern-di",
"that-depends",
"lite-bootstrap",
"httpware",
"faststream-redis-timers",
"faststream-outbox",
"semvertag",
}
CARD_ALLOWED = {
c.lower()
for c in (
t.GREEN_INK,
t.GREEN_SURFACE,
t.GOLD_LIGHT,
t.GOLD_DARK,
t.CREAM,
t.GREEN_MUTED,
)
}
def test_docs_repos_is_subset_of_manifest_and_exact() -> None:
assert set(p.DOCS_REPOS) == DOCS_EXPECTED
assert set(p.DOCS_REPOS) <= set(p.MANIFEST)
@pytest.mark.parametrize("repo", sorted(DOCS_EXPECTED))
def test_social_card_valid_and_palette(repo: str) -> None:
svg = p.project_social_card(repo, tagline=p.DOCS_REPOS[repo])
minidom.parseString(svg)
assert 'viewBox="0 0 1280 640"' in svg
hexes = {h.lower() for h in re.findall(r"#[0-9a-fA-F]{6}", svg)}
assert hexes <= CARD_ALLOWED, f"{repo} stray colours: {hexes - CARD_ALLOWED}"
def test_social_card_includes_url_and_name(repo: str = "modern-di") -> None:
svg = p.project_social_card(repo, tagline=p.DOCS_REPOS[repo])
assert f'aria-label="{repo}' in svg # accessible label carries the repo
def test_render_projects_writes_cards_for_docs_repos_only(tmp_path: Path) -> None:
p.render_projects(out_dir=tmp_path)
for repo in DOCS_EXPECTED:
card = tmp_path / repo / "social-card.svg"
assert card.is_file() and card.read_text(encoding="utf-8").startswith("<svg")
non_docs = set(p.MANIFEST) - DOCS_EXPECTED
for repo in non_docs:
assert not (tmp_path / repo / "social-card.svg").exists()
# Regression guard for the transparent-background invariant: every mark is
# drawn on a transparent background, and CREAM is only ever a knockout
# painted ON TOP OF a GOLD shape — never standalone ink directly on the
# background. Violate that and the mark looks fine on a cream/light page
# (cream-on-cream is invisible) but shows stray bright-white ink once the
# same mark.svg is placed on a dark surface (e.g. lockup-dark.svg's README
# banner half). Parametrized over EXPECTED_REPOS (the full manifest, like
# every other guard in this file) so a repo added later is covered
# automatically instead of needing to be opted in.
#
def _cream_pixels_without_gold_beneath(
repo: str, tmp_path: Path, size: int = 256
) -> list[tuple[int, int]]:
"""Rasterize `repo`'s mark twice — once normally, once with every CREAM
knockout stripped to `none` — and return the (x, y) of every pixel that
is CREAM in the normal render but transparent once CREAM is stripped.
Such a pixel has no gold underneath it: it is standalone cream ink
sitting directly on the transparent background."""
svg = p.project_mark(repo)
normal_svg = tmp_path / f"{repo}-normal.svg"
normal_svg.write_text(svg, encoding="utf-8")
normal_png = tmp_path / f"{repo}-normal.png"
assert export_png(normal_svg, normal_png, width=size, height=size)
stripped = svg.replace(t.CREAM, "none")
# A literal, case-sensitive replace silently no-ops if cream were ever
# written in a different form (e.g. uppercase hex, rgb()) — make that
# loud instead of letting the guard pass vacuously against an unchanged
# (still-opaque) render.
if t.CREAM in svg:
assert stripped != svg, (
f"{repo}: stripping {t.CREAM} left the SVG unchanged — "
"the guard would compare a render against itself"
)
stripped_svg = tmp_path / f"{repo}-stripped.svg"
stripped_svg.write_text(stripped, encoding="utf-8")
stripped_png = tmp_path / f"{repo}-stripped.png"
assert export_png(stripped_svg, stripped_png, width=size, height=size)
normal_im = Image.open(normal_png).convert("RGBA")
stripped_im = Image.open(stripped_png).convert("RGBA")
cream_rgb = tuple(int(t.CREAM[i : i + 2], 16) for i in (1, 3, 5))
tol = 10
bad: list[tuple[int, int]] = []
for idx, ((r, gg, b, a), (_, _, _, sa)) in enumerate(
zip(normal_im.getdata(), stripped_im.getdata())
):
if a < 200:
continue
if (
abs(r - cream_rgb[0]) < tol
and abs(gg - cream_rgb[1]) < tol
and abs(b - cream_rgb[2]) < tol
and sa < 50
):
bad.append((idx % size, idx // size))
return bad
@pytest.mark.skipif(
shutil.which("rsvg-convert") is None, reason="rsvg-convert not installed"
)
@pytest.mark.parametrize("repo", sorted(EXPECTED_REPOS))
def test_no_cream_on_transparent(repo: str, tmp_path: Path) -> None:
"""INVARIANT: cream is only ever a knockout painted on top of gold, never ink
directly on the transparent background.
Marks render on dark surfaces too — `lockup-dark.svg` is the dark half of every
README `<picture>` banner — so standalone cream is invisible on a light page and
stray bright-white ink on a dark one. Any new or edited inner symbol breaks this
the moment a cream shape pokes past the gold beneath it: a round cap spilling past
a hull, a band past a capsule, an eye in a hollow counter. Applies to every
manifest repo with no exemptions.
"""
bad = _cream_pixels_without_gold_beneath(repo, tmp_path)
assert not bad, (
f"{repo}: {len(bad)} cream pixel(s) with no gold beneath, e.g. {bad[:5]} "
"— this mark will show stray white ink on dark surfaces"
)