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

1"""Hatch command — generate bee-themed example features directory.""" 

2 

3from __future__ import annotations 

4 

5import secrets 

6from dataclasses import dataclass 

7from pathlib import Path 

8 

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] 

21 

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"] 

33 

34_BACKLOG_CONTENT = """\ 

35Feature: {feature_name} 

36 

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 

40 

41 Rule: Forager readiness 

42 

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 

48 

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 

53 

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 

59 

60 Rule: Nectar quality control 

61 

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""" 

68 

69_IN_PROGRESS_CONTENT = """\ 

70# language: en 

71Feature: Waggle Dance Communication 

72 

73 Background: 

74 Given the hive is in active foraging mode 

75 And the dance floor is clear of obstacles 

76 

77 Rule: Direction encoding 

78 

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 

84 

85 Rule: Distance encoding 

86 

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 

92 

93 Examples: 

94 | distance | duration | 

95 | 100 | 250 | 

96 | 500 | 875 | 

97 | 1000 | 1500 | 

98 

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""" 

109 

110_COMPLETED_CONTENT = """\ 

111Feature: Colony Winter Preparation 

112 

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 

116 

117 Rule: Honey reserve verification 

118 

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 

124 

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""" 

131 

132 

133@dataclass(frozen=True, slots=True) 

134class HatchFile: 

135 """A single generated .feature file to be written. 

136 

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 """ 

141 

142 relative_path: str 

143 content: str 

144 

145 

146def generate_hatch_files() -> list[HatchFile]: 

147 """Generate bee-themed example .feature files using stdlib randomisation. 

148 

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) 

155 

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 ] 

172 

173 

174def write_hatch(features_root: Path, files: list[HatchFile]) -> list[str]: 

175 """Write HatchFile objects to disk under features_root. 

176 

177 Args: 

178 features_root: The root features directory to write into. 

179 files: The list of HatchFile objects to write. 

180 

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 

191 

192 

193def run_hatch(features_root: Path, force: bool) -> list[str]: 

194 """Run the hatch command: check for conflicts, generate, and write files. 

195 

196 Args: 

197 features_root: The root features directory to populate. 

198 force: If True, overwrite existing content without error. 

199 

200 Returns: 

201 List of written file paths as strings. 

202 

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())