Coverage for src/jtech_installer/validator/integrity.py: 97%

97 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-08-20 15:10 -0300

1"""Integrity validator for JTECH™ Installer""" 

2 

3import hashlib 

4from dataclasses import dataclass 

5from pathlib import Path 

6from typing import Dict, List, Optional 

7 

8from ..core.exceptions import JTechInstallerException 

9from ..core.models import InstallationConfig 

10 

11 

12@dataclass 

13class IntegrityCheckResult: 

14 """Resultado de verificação de integridade""" 

15 

16 component: str 

17 is_valid: bool 

18 expected_checksum: Optional[str] = None 

19 actual_checksum: Optional[str] = None 

20 error_message: Optional[str] = None 

21 

22 

23class IntegrityValidator: 

24 """Valida a integridade da instalação""" 

25 

26 def __init__(self, config: InstallationConfig): 

27 self.config = config 

28 self.project_path = config.project_path 

29 self.jtech_core_path = self.project_path / ".jtech-core" 

30 

31 def validate_all(self) -> bool: 

32 """Executa todas as validações de integridade""" 

33 results = [] 

34 

35 # Validar estrutura de diretórios 

36 results.extend(self._validate_directory_structure()) 

37 

38 # Validar arquivos de configuração 

39 results.extend(self._validate_config_files()) 

40 

41 # Validar agentes instalados 

42 results.extend(self._validate_agents()) 

43 

44 # Validar chatmodes 

45 results.extend(self._validate_chatmodes()) 

46 

47 # Verificar se há falhas 

48 failed_checks = [r for r in results if not r.is_valid] 

49 

50 if failed_checks: 

51 for failure in failed_checks: 

52 print(f"{failure.component}: {failure.error_message}") 

53 return False 

54 

55 print(f"✅ Todos os {len(results)} checks de integridade passaram") 

56 return True 

57 

58 def _validate_directory_structure(self) -> List[IntegrityCheckResult]: 

59 """Valida se a estrutura de diretórios está correta""" 

60 results = [] 

61 

62 required_dirs = [ 

63 ".jtech-core", 

64 ".jtech-core/agents", 

65 ".jtech-core/chatmodes", 

66 ".jtech-core/templates", 

67 ".jtech-core/workflows", 

68 ".jtech-core/tasks", 

69 ".jtech-core/checklists", 

70 ".jtech-core/data", 

71 ".jtech-core/utils", 

72 ] 

73 

74 for dir_path in required_dirs: 

75 full_path = self.project_path / dir_path 

76 if full_path.exists() and full_path.is_dir(): 

77 results.append( 

78 IntegrityCheckResult( 

79 component=f"Directory {dir_path}", is_valid=True 

80 ) 

81 ) 

82 else: 

83 results.append( 

84 IntegrityCheckResult( 

85 component=f"Directory {dir_path}", 

86 is_valid=False, 

87 error_message=f"Diretório {dir_path} não existe", 

88 ) 

89 ) 

90 

91 return results 

92 

93 def _validate_config_files(self) -> List[IntegrityCheckResult]: 

94 """Valida arquivos de configuração críticos""" 

95 results = [] 

96 

97 # Verificar core-config.yml 

98 core_config = self.jtech_core_path / "core-config.yml" 

99 if core_config.exists(): 

100 results.append( 

101 IntegrityCheckResult( 

102 component="core-config.yml", is_valid=True 

103 ) 

104 ) 

105 else: 

106 results.append( 

107 IntegrityCheckResult( 

108 component="core-config.yml", 

109 is_valid=False, 

110 error_message="Arquivo core-config.yml não encontrado", 

111 ) 

112 ) 

113 

114 # Verificar .vscode/settings.json se VS Code habilitado 

115 if self.config.vs_code_integration: 

116 vscode_settings = self.project_path / ".vscode" / "settings.json" 

117 if vscode_settings.exists(): 

118 results.append( 

119 IntegrityCheckResult( 

120 component=".vscode/settings.json", is_valid=True 

121 ) 

122 ) 

123 else: 

124 results.append( 

125 IntegrityCheckResult( 

126 component=".vscode/settings.json", 

127 is_valid=False, 

128 error_message="Arquivo settings.json não encontrado", 

129 ) 

130 ) 

131 

132 return results 

133 

134 def _validate_agents(self) -> List[IntegrityCheckResult]: 

135 """Valida integridade dos agentes instalados""" 

136 results = [] 

137 agents_dir = self.jtech_core_path / "agents" 

138 

139 if not agents_dir.exists(): 

140 results.append( 

141 IntegrityCheckResult( 

142 component="Agents directory", 

143 is_valid=False, 

144 error_message="Diretório de agentes não existe", 

145 ) 

146 ) 

147 return results 

148 

149 # Verificar se há pelo menos alguns agentes básicos 

150 basic_agents = ["pm.md", "architect.md", "dev.md"] 

151 found_agents = 0 

152 

153 for agent_file in basic_agents: 

154 agent_path = agents_dir / agent_file 

155 if agent_path.exists(): 

156 found_agents += 1 

157 results.append( 

158 IntegrityCheckResult( 

159 component=f"Agent {agent_file}", is_valid=True 

160 ) 

161 ) 

162 else: 

163 results.append( 

164 IntegrityCheckResult( 

165 component=f"Agent {agent_file}", 

166 is_valid=False, 

167 error_message=f"Agente {agent_file} não encontrado", 

168 ) 

169 ) 

170 

171 return results 

172 

173 def _validate_chatmodes(self) -> List[IntegrityCheckResult]: 

174 """Valida chatmodes instalados""" 

175 results = [] 

176 chatmodes_dir = self.project_path / ".github" / "chatmodes" 

177 

178 if not chatmodes_dir.exists(): 

179 results.append( 

180 IntegrityCheckResult( 

181 component="Chatmodes directory", 

182 is_valid=False, 

183 error_message="Diretório .github/chatmodes não existe", 

184 ) 

185 ) 

186 return results 

187 

188 # Contar arquivos .chatmode.md 

189 chatmode_files = list(chatmodes_dir.glob("*.chatmode.md")) 

190 

191 if len(chatmode_files) > 0: 

192 results.append( 

193 IntegrityCheckResult( 

194 component=f"Chatmodes ({len(chatmode_files)} arquivos)", 

195 is_valid=True, 

196 ) 

197 ) 

198 else: 

199 results.append( 

200 IntegrityCheckResult( 

201 component="Chatmodes", 

202 is_valid=False, 

203 error_message="Nenhum arquivo .chatmode.md encontrado", 

204 ) 

205 ) 

206 

207 return results 

208 

209 def calculate_file_checksum(self, file_path: Path) -> str: 

210 """Calcula checksum SHA256 de um arquivo""" 

211 if not file_path.exists(): 

212 raise JTechInstallerException( 

213 f"Arquivo não encontrado: {file_path}" 

214 ) 

215 

216 sha256_hash = hashlib.sha256() 

217 with open(file_path, "rb") as f: 

218 for chunk in iter(lambda: f.read(4096), b""): 

219 sha256_hash.update(chunk) 

220 return sha256_hash.hexdigest() 

221 

222 def verify_checksums( 

223 self, expected_checksums: Dict[str, str] 

224 ) -> List[IntegrityCheckResult]: 

225 """Verifica checksums de arquivos específicos""" 

226 results = [] 

227 

228 for relative_path, expected_checksum in expected_checksums.items(): 

229 file_path = self.project_path / relative_path 

230 

231 try: 

232 actual_checksum = self.calculate_file_checksum(file_path) 

233 is_valid = actual_checksum == expected_checksum 

234 

235 results.append( 

236 IntegrityCheckResult( 

237 component=f"Checksum {relative_path}", 

238 is_valid=is_valid, 

239 expected_checksum=expected_checksum, 

240 actual_checksum=actual_checksum, 

241 error_message=( 

242 None if is_valid else "Checksum não confere" 

243 ), 

244 ) 

245 ) 

246 except Exception as e: 

247 results.append( 

248 IntegrityCheckResult( 

249 component=f"Checksum {relative_path}", 

250 is_valid=False, 

251 error_message=str(e), 

252 ) 

253 ) 

254 

255 return results