Coverage for pytest_beehave/hatch.py: 100%
35 statements
« prev ^ index » next coverage.py v7.8.0, created at 2026-04-21 04:49 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2026-04-21 04:49 +0000
1"""Hatch command — generate bee-themed example features directory."""
3from __future__ import annotations
5import secrets
6from dataclasses import dataclass
7from pathlib import Path
9_FEATURE_NAMES = [
10 "The Forager's Journey",
11 "Queen's Decree",
12 "Drone Assembly Protocol",
13 "Worker Bee Orientation",
14 "Nectar Collection Workflow",
15 "Hive Temperature Regulation",
16 "Pollen Scout Dispatch",
17 "Royal Jelly Production",
18 "Swarm Formation Ritual",
19 "Honeycomb Architecture Review",
20]
22_BEES = [
23 "Beatrice",
24 "Boris",
25 "Belinda",
26 "Bruno",
27 "Blossom",
28 "Barnaby",
29 "Bridget",
30 "Bertram",
31]
32_HIVES = ["the Golden Hive", "the Amber Hive", "the Crystal Hive", "the Obsidian Hive"]
34_BACKLOG_CONTENT = """\
35Feature: {feature_name}
37 As {bee}, a worker bee in {hive}
38 I want to complete my assigned foraging route
39 So that the colony has enough nectar for the season
41 Rule: Forager readiness
43 @id:hatch001
44 Example: Forager departs when pollen reserve is below threshold
45 Given the pollen reserve is below 30 percent
46 When the forager sensor detects the shortage
47 Then {bee} departs for the meadow within one waggle cycle
49 Example: Untagged scenario triggers auto-ID assignment
50 Given the hive registers a new forager
51 When the forager completes orientation
52 Then the forager is assigned a unique scout ID
54 @deprecated
55 Example: Legacy hive-entry handshake (deprecated)
56 Given an older forager approaches the hive entrance
57 When the guard bee checks the legacy handshake
58 Then the handshake is accepted but logged as deprecated
60 Rule: Nectar quality control
62 @id:hatch002
63 Example: Low-quality nectar is rejected at the gate
64 Given a forager returns with nectar of quality below 0.4 brix
65 When the gate inspector evaluates the sample
66 Then the nectar is rejected and the forager is sent to a higher-quality source
67"""
69_IN_PROGRESS_CONTENT = """\
70# language: en
71Feature: Waggle Dance Communication
73 Background:
74 Given the hive is in active foraging mode
75 And the dance floor is clear of obstacles
77 Rule: Direction encoding
79 @id:hatch003
80 Example: Scout encodes flower direction in waggle run angle
81 Given a scout has located flowers 200 metres to the north-east
82 When the scout performs the waggle dance
83 Then the waggle run angle matches the sun-relative bearing to the flowers
85 Rule: Distance encoding
87 @id:hatch004
88 Scenario Outline: Scout encodes distance via waggle run duration
89 Given a scout has located flowers at <distance> metres
90 When the scout performs the waggle dance
91 Then the waggle run lasts approximately <duration> milliseconds
93 Examples:
94 | distance | duration |
95 | 100 | 250 |
96 | 500 | 875 |
97 | 1000 | 1500 |
99 @id:hatch005
100 Example: Scout provides a data table of visited flower patches
101 Given the scout returns from a multi-patch forage
102 When the scout performs the waggle dance
103 Then the flower patch register contains the following entries:
104 | patch_id | species | quality |
105 | P-001 | Lavender | 0.92 |
106 | P-002 | Clover | 0.85 |
107 | P-003 | Sunflower | 0.78 |
108"""
110_COMPLETED_CONTENT = """\
111Feature: Colony Winter Preparation
113 As {bee}, the winter logistics coordinator in {hive}
114 I want to ensure honey stores are sufficient before the first frost
115 So that the colony survives the winter without starvation
117 Rule: Honey reserve verification
119 @id:hatch006
120 Example: Winter preparation passes when honey reserve exceeds minimum
121 Given the honey reserve is at 85 percent capacity
122 When the winter readiness check is performed
123 Then the colony status is set to WINTER-READY
125 @id:hatch007
126 Example: Winter preparation fails when honey reserve is insufficient
127 Given the honey reserve is below 60 percent capacity
128 When the winter readiness check is performed
129 Then the colony status is set to AT-RISK and an alert is raised for {bee}
130"""
133@dataclass(frozen=True, slots=True)
134class HatchFile:
135 """A single generated .feature file to be written.
137 Attributes:
138 relative_path: Path relative to the features root (e.g. ``backlog/x.feature``).
139 content: The full Gherkin text to write.
140 """
142 relative_path: str
143 content: str
146def generate_hatch_files() -> list[HatchFile]:
147 """Generate bee-themed example .feature files using stdlib randomisation.
149 Returns:
150 A list of HatchFile objects ready to be written to disk.
151 """
152 feature_name = secrets.choice(_FEATURE_NAMES)
153 bee = secrets.choice(_BEES)
154 hive = secrets.choice(_HIVES)
156 return [
157 HatchFile(
158 relative_path="backlog/forager-journey.feature",
159 content=_BACKLOG_CONTENT.format(
160 feature_name=feature_name, bee=bee, hive=hive
161 ),
162 ),
163 HatchFile(
164 relative_path="in-progress/waggle-dance.feature",
165 content=_IN_PROGRESS_CONTENT,
166 ),
167 HatchFile(
168 relative_path="completed/winter-preparation.feature",
169 content=_COMPLETED_CONTENT.format(bee=bee, hive=hive),
170 ),
171 ]
174def write_hatch(features_root: Path, files: list[HatchFile]) -> list[str]:
175 """Write HatchFile objects to disk under features_root.
177 Args:
178 features_root: The root features directory to write into.
179 files: The list of HatchFile objects to write.
181 Returns:
182 List of written file paths as strings (relative to features_root).
183 """
184 written: list[str] = []
185 for hatch_file in files:
186 dest = features_root / hatch_file.relative_path
187 dest.parent.mkdir(parents=True, exist_ok=True)
188 dest.write_text(hatch_file.content, encoding="utf-8")
189 written.append(hatch_file.relative_path)
190 return written
193def run_hatch(features_root: Path, force: bool) -> list[str]:
194 """Run the hatch command: check for conflicts, generate, and write files.
196 Args:
197 features_root: The root features directory to populate.
198 force: If True, overwrite existing content without error.
200 Returns:
201 List of written file paths as strings.
203 Raises:
204 SystemExit: If the directory contains .feature files and force is False.
205 """
206 existing = list(features_root.rglob("*.feature")) if features_root.exists() else []
207 if existing and not force:
208 conflict = existing[0]
209 raise SystemExit(
210 f"[beehave] hatch aborted: existing .feature files found at {conflict}. "
211 "Use --beehave-hatch-force to overwrite."
212 )
213 for old_file in existing:
214 old_file.unlink()
215 return write_hatch(features_root, generate_hatch_files())