Skip to content

Commit 7e16cfc

Browse files
committed
[red] Guard pre-activation public claims
Signed-off-by: Viwat Vchirawongkwin <viwat.v@chula.ac.th>
1 parent 1408b99 commit 7e16cfc

2 files changed

Lines changed: 154 additions & 19 deletions

File tree

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# SPDX-License-Identifier: MIT
2+
# Part of PyBLE (https://pyble.dev) — see /LICENSE.
3+
4+
from __future__ import annotations
5+
6+
import unittest
7+
from pathlib import Path
8+
9+
REPO_ROOT = Path(__file__).resolve().parents[2]
10+
11+
12+
def markdown_section(document: str, heading: str) -> str:
13+
marker = f"## {heading}\n"
14+
start = document.index(marker) + len(marker)
15+
end = document.find("\n## ", start)
16+
return document[start:] if end < 0 else document[start:end]
17+
18+
19+
class PublicClaimsTest(unittest.TestCase):
20+
@classmethod
21+
def setUpClass(cls) -> None:
22+
cls.readme = (REPO_ROOT / "README.md").read_text(encoding="utf-8")
23+
cls.bug_template = (
24+
REPO_ROOT / ".github" / "ISSUE_TEMPLATE" / "bug.yml"
25+
).read_text(encoding="utf-8")
26+
27+
def test_readme_is_truthful_before_v042_hil_completes(self) -> None:
28+
firmware = markdown_section(self.readme, "What works")
29+
30+
self.assertIn(
31+
"public browser installer is currently unavailable pending v0.4.2 HIL",
32+
firmware,
33+
)
34+
self.assertIn("`esp32-4mb`", firmware)
35+
self.assertIn("Classic ESP32, 4 MiB external SPI flash", firmware)
36+
self.assertIn("`esp32-s3-n16r8`", firmware)
37+
self.assertIn("16 MiB flash / 8 MiB Octal PSRAM", firmware)
38+
self.assertNotIn("currently offers qualified images", firmware)
39+
self.assertNotIn("| Available", firmware)
40+
41+
def test_readme_caption_describes_only_the_visible_app(self) -> None:
42+
caption_start = self.readme.index("<em>Actual PyBLE app")
43+
caption_end = self.readme.index("</em>", caption_start)
44+
caption = self.readme[caption_start:caption_end]
45+
46+
self.assertIn("in landscape", caption)
47+
self.assertIn("GPIO 48 NeoPixel Blocks", caption)
48+
self.assertIn("generated MicroPython", caption)
49+
self.assertNotRegex(caption, r"(?i)pictured|board|module")
50+
51+
def test_readme_try_steps_are_gated_on_an_active_installer(self) -> None:
52+
try_section = markdown_section(self.readme, "Try PyBLE")
53+
54+
self.assertIn("currently unavailable pending v0.4.2 HIL", try_section)
55+
self.assertIn("active release version", try_section)
56+
self.assertIn("enabled install action", try_section)
57+
self.assertNotRegex(try_section, r"(?is)select .*qualified\s+agent firmware")
58+
59+
def test_bug_template_collects_the_exact_installer_diagnostics(self) -> None:
60+
for field_id in (
61+
"profile",
62+
"module",
63+
"memory",
64+
"browser",
65+
"operating_system",
66+
"installer_stage",
67+
):
68+
self.assertIn(f" id: {field_id}\n", self.bug_template)
69+
70+
for wording in (
71+
"Exact installer profile ID",
72+
"Exact board model and module marking",
73+
"Flash capacity, PSRAM capacity, and PSRAM type",
74+
"Browser name and exact version",
75+
"Desktop operating system and exact version",
76+
"Failed installer stage",
77+
):
78+
self.assertIn(wording, self.bug_template)
79+
80+
self.assertRegex(self.bug_template, r"(?i)remove.*(?:secret|credential)")
81+
82+
83+
if __name__ == "__main__":
84+
unittest.main()

‎tools/web/src/test/site-contract.test.tsx‎

Lines changed: 70 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,12 @@ describe("public-site contract", () => {
9999
});
100100

101101
const publicDirectory = join(process.cwd(), "public");
102-
const [socialPng, qrCardPng, qrCardSvg] = await Promise.all([
102+
const [socialPng, socialSvg, qrCardPng, qrCardSvg] = await Promise.all([
103103
readFile(join(publicDirectory, "social", "pyble-beta-og-1200x630.png")),
104+
readFile(
105+
join(publicDirectory, "social", "pyble-beta-og-1200x630.svg"),
106+
"utf8",
107+
),
104108
readFile(join(publicDirectory, "social", "pyble-testflight-qr-1080.png")),
105109
readFile(
106110
join(publicDirectory, "social", "pyble-testflight-qr-1080.svg"),
@@ -110,6 +114,13 @@ describe("public-site contract", () => {
110114
expect([socialPng.readUInt32BE(16), socialPng.readUInt32BE(20)]).toEqual([
111115
1200, 630,
112116
]);
117+
expect(socialSvg).toContain("One-time USB setup.");
118+
expect(socialSvg).toContain("Everyday coding over BLE.");
119+
expect(socialSvg).toContain("FIRMWARE HIL PENDING");
120+
expect(socialSvg).not.toContain("WEB FLASHER");
121+
expect(createHash("sha256").update(socialPng).digest("hex")).not.toBe(
122+
"c6ff10039c00780909b79b0e557c46b843d9994bc0d545a9a098488c3157ff1d",
123+
);
113124
expect([qrCardPng.readUInt32BE(16), qrCardPng.readUInt32BE(20)]).toEqual([
114125
1080, 1080,
115126
]);
@@ -138,7 +149,7 @@ describe("public-site contract", () => {
138149
);
139150
});
140151

141-
it("states the vendor-neutral vision and separates it from initial firmware support", () => {
152+
it("states the vendor-neutral vision and the truthful pre-activation firmware state", () => {
142153
render(<HomePage />);
143154

144155
expect(
@@ -152,11 +163,9 @@ describe("public-site contract", () => {
152163
/designed for boards that run MicroPython and support Bluetooth Low Energy/i,
153164
),
154165
).toBeInTheDocument();
155-
expect(
156-
screen.getByText(
157-
/current firmware release supports qualified classic ESP32 and ESP32-S3 module profiles/i,
158-
),
159-
).toBeInTheDocument();
166+
expect(screen.getByText(/v0\.4\.2 firmware candidate/i)).toHaveTextContent(
167+
/public browser installer stays unavailable until both exact profiles pass HIL/i,
168+
);
160169
expect(
161170
screen.getByText(
162171
/ESP32-C3 and more microcontroller families remain planned/i,
@@ -304,17 +313,39 @@ describe("public-site contract", () => {
304313
expect(capture.readUInt32BE(20)).toBe(2732);
305314
});
306315

307-
it("distinguishes current firmware families from the planned ESP32-C3 target", () => {
316+
it("names every exact profile, memory constraint, and qualification state", () => {
308317
expect(initialFirmwareTargets).toEqual([
309-
{ name: "ESP32", availability: "Released profile" },
310-
{ name: "ESP32-S3", availability: "Released profile" },
311-
{ name: "ESP32-C3", availability: "Planned profile" },
318+
{
319+
id: "esp32-4mb",
320+
target: "Classic ESP32",
321+
constraint: "4 MiB external SPI flash · no PSRAM assumed",
322+
status: "v0.4.2 HIL pending · installer unavailable",
323+
planned: false,
324+
},
325+
{
326+
id: "esp32-s3-n16r8",
327+
target: "ESP32-S3 N16R8",
328+
constraint: "16 MiB flash · 8 MiB Octal PSRAM",
329+
status: "v0.4.2 HIL pending · installer unavailable",
330+
planned: false,
331+
},
332+
{
333+
id: "esp32-c3-4mb",
334+
target: "ESP32-C3",
335+
constraint: "4 MiB external SPI flash · no PSRAM assumed",
336+
status: "Planned · installer unavailable pending exact-profile HIL",
337+
planned: true,
338+
},
312339
]);
313340

314341
render(<HomePage />);
315342

316-
const esp32C3Target = screen.getByText("ESP32-C3").closest("div");
317-
expect(esp32C3Target).toHaveTextContent("Planned profile");
343+
for (const target of initialFirmwareTargets) {
344+
const targetCard = screen.getByText(target.id).closest("div");
345+
expect(targetCard).toHaveTextContent(target.target);
346+
expect(targetCard).toHaveTextContent(target.constraint);
347+
expect(targetCard).toHaveTextContent(target.status);
348+
}
318349
});
319350

320351
it("keeps the public installer unavailable while explaining exact profiles, BLE use, and recovery", () => {
@@ -472,16 +503,30 @@ describe("public-site contract", () => {
472503
expect(screen.getByText(/ordinary request data/i)).toBeInTheDocument();
473504
});
474505

475-
it("gives beta users a concrete support checklist and contact", () => {
506+
it("gives beta users an exact installer intake and preferred bug template", () => {
476507
render(<SupportPage />);
477508

478509
expect(
479510
screen.getByRole("heading", { level: 1, name: "Support" }),
480511
).toBeInTheDocument();
481-
expect(screen.getByText("Board and chip family")).toBeInTheDocument();
512+
expect(
513+
screen.getByText(/exact installer profile ID/i),
514+
).toBeInTheDocument();
515+
expect(
516+
screen.getByText(/exact board model and module marking/i),
517+
).toBeInTheDocument();
518+
expect(
519+
screen.getByText(/flash capacity, PSRAM capacity, and PSRAM type/i),
520+
).toBeInTheDocument();
521+
expect(
522+
screen.getByText(/browser name\/version and desktop OS name\/version/i),
523+
).toBeInTheDocument();
524+
expect(
525+
screen.getByText(/failed installer stage and redacted error text/i),
526+
).toBeInTheDocument();
482527
expect(
483528
screen.getByText(
484-
/public installer supports only esp32-4mb and esp32-s3-n16r8/i,
529+
/public installer is unavailable while v0\.4\.2 HIL runs for esp32-4mb and esp32-s3-n16r8/i,
485530
),
486531
).toBeInTheDocument();
487532
expect(
@@ -495,8 +540,14 @@ describe("public-site contract", () => {
495540
expect(
496541
screen.getByText("PyBLE app and agent versions"),
497542
).toBeInTheDocument();
498-
expect(
499-
screen.getByRole("link", { name: "viwat.v@chula.ac.th" }),
500-
).toHaveAttribute("href", "mailto:viwat.v@chula.ac.th");
543+
expect(siteConfig.bugReportUrl).toBe(
544+
"https://github.com/PyBLE-dev/PyBLE/issues/new?template=bug.yml",
545+
);
546+
const preferredReport = screen.getByRole("link", {
547+
name: "Open the GitHub bug template",
548+
});
549+
expect(preferredReport).toHaveAttribute("href", siteConfig.bugReportUrl);
550+
expect(preferredReport).toHaveAttribute("target", "_blank");
551+
expect(preferredReport).toHaveAttribute("rel", "noopener noreferrer");
501552
});
502553
});

0 commit comments

Comments
 (0)