|
14 | 14 | $rendered = $this->renderer->render($this->stubLoader->load('issue'), $record); |
15 | 15 |
|
16 | 16 | expect($rendered) |
17 | | - ->toContain('**Log Level:** ERROR') |
18 | | - ->toContain('**Message:** Test message'); |
| 17 | + ->toContain('**Level:** ERROR') |
| 18 | + ->toContain('**Message:** Test message') |
| 19 | + ->toContain('## Triage Information'); |
19 | 20 | }); |
20 | 21 |
|
21 | 22 | test('it renders title without exception', function () { |
|
40 | 41 | $rendered = $this->renderer->render($this->stubLoader->load('issue'), $record); |
41 | 42 |
|
42 | 43 | expect($rendered) |
43 | | - ->toContain('## Context') |
| 44 | + ->toContain('<summary>📦 Context</summary>') |
44 | 45 | ->toContain('"user_id": 123'); |
45 | 46 | }); |
46 | 47 |
|
|
50 | 51 | $rendered = $this->renderer->render($this->stubLoader->load('issue'), $record); |
51 | 52 |
|
52 | 53 | expect($rendered) |
53 | | - ->toContain('## Extra Data') |
| 54 | + ->toContain('<summary>➕ Extra Data</summary>') |
54 | 55 | ->toContain('"request_id": "abc123"'); |
55 | 56 | }); |
56 | 57 |
|
|
119 | 120 | ->not->toContain('Stack trace:') |
120 | 121 | ->not->toContain('#0 /path/to/app/Services/PaymentService.php'); |
121 | 122 |
|
122 | | - // Should have class populated (even if generic) |
| 123 | + // Should have exception class populated (even if generic) |
123 | 124 | expect($rendered) |
124 | | - ->toContain('**Class:**') |
125 | | - ->toMatch('/\*\*Class:\*\* .+/'); // Class should have a value |
| 125 | + ->toContain('**Exception:**') |
| 126 | + ->toMatch('/\*\*Exception:\*\* .+/'); // Exception should have a value |
126 | 127 |
|
127 | 128 | // Should have stack traces populated |
128 | 129 | expect($rendered) |
129 | | - ->toContain('## Stack Trace') |
| 130 | + ->toContain('<summary>📋 Stack Trace</summary>') |
130 | 131 | ->toContain('[stacktrace]') |
131 | 132 | ->toContain('App\\Calculations\\Calculator->calculate()'); |
132 | 133 | }); |
| 134 | + |
| 135 | +test('it formats timestamp placeholder correctly', function () { |
| 136 | + $record = createLogRecord('Test message'); |
| 137 | + |
| 138 | + $rendered = $this->renderer->render($this->stubLoader->load('issue'), $record); |
| 139 | + |
| 140 | + expect($rendered) |
| 141 | + ->toContain('**Timestamp:**') |
| 142 | + ->toMatch('/\*\*Timestamp:\*\* \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/'); |
| 143 | +}); |
| 144 | + |
| 145 | +test('it formats route summary as method and path', function () { |
| 146 | + $record = createLogRecord('Test message', [ |
| 147 | + 'request' => [ |
| 148 | + 'method' => 'POST', |
| 149 | + 'url' => 'https://example.com/api/users', |
| 150 | + ], |
| 151 | + ]); |
| 152 | + |
| 153 | + $rendered = $this->renderer->render($this->stubLoader->load('issue'), $record); |
| 154 | + |
| 155 | + expect($rendered) |
| 156 | + ->toContain('**Route:** POST /api/users'); |
| 157 | +}); |
| 158 | + |
| 159 | +test('it returns empty string for route summary when request data is missing', function () { |
| 160 | + $record = createLogRecord('Test message'); |
| 161 | + |
| 162 | + $rendered = $this->renderer->render($this->stubLoader->load('issue'), $record); |
| 163 | + |
| 164 | + expect($rendered) |
| 165 | + ->toContain('**Route:**'); |
| 166 | +}); |
| 167 | + |
| 168 | +test('it formats user summary with user id', function () { |
| 169 | + $record = createLogRecord('Test message', [ |
| 170 | + 'user' => [ |
| 171 | + 'id' => 123, |
| 172 | + ], |
| 173 | + ]); |
| 174 | + |
| 175 | + $rendered = $this->renderer->render($this->stubLoader->load('issue'), $record); |
| 176 | + |
| 177 | + expect($rendered) |
| 178 | + ->toContain('**User:** User ID: 123'); |
| 179 | +}); |
| 180 | + |
| 181 | +test('it formats user summary as unauthenticated when user data is missing', function () { |
| 182 | + $record = createLogRecord('Test message'); |
| 183 | + |
| 184 | + $rendered = $this->renderer->render($this->stubLoader->load('issue'), $record); |
| 185 | + |
| 186 | + expect($rendered) |
| 187 | + ->toContain('**User:** Unauthenticated'); |
| 188 | +}); |
| 189 | + |
| 190 | +test('it formats user summary as unauthenticated when user id is missing', function () { |
| 191 | + $record = createLogRecord('Test message', [ |
| 192 | + 'user' => [ |
| 193 | + 'name' => 'John Doe', |
| 194 | + ], |
| 195 | + ]); |
| 196 | + |
| 197 | + $rendered = $this->renderer->render($this->stubLoader->load('issue'), $record); |
| 198 | + |
| 199 | + expect($rendered) |
| 200 | + ->toContain('**User:** Unauthenticated'); |
| 201 | +}); |
| 202 | + |
| 203 | +test('it extracts environment name from environment context', function () { |
| 204 | + $record = createLogRecord('Test message', [ |
| 205 | + 'environment' => [ |
| 206 | + 'APP_ENV' => 'production', |
| 207 | + ], |
| 208 | + ]); |
| 209 | + |
| 210 | + $rendered = $this->renderer->render($this->stubLoader->load('issue'), $record); |
| 211 | + |
| 212 | + expect($rendered) |
| 213 | + ->toContain('**Environment:** production'); |
| 214 | +}); |
| 215 | + |
| 216 | +test('it extracts environment name from lowercase app_env key', function () { |
| 217 | + $record = createLogRecord('Test message', [ |
| 218 | + 'environment' => [ |
| 219 | + 'app_env' => 'staging', |
| 220 | + ], |
| 221 | + ]); |
| 222 | + |
| 223 | + $rendered = $this->renderer->render($this->stubLoader->load('issue'), $record); |
| 224 | + |
| 225 | + expect($rendered) |
| 226 | + ->toContain('**Environment:** staging'); |
| 227 | +}); |
| 228 | + |
| 229 | +test('it returns empty string for environment name when not available', function () { |
| 230 | + $record = createLogRecord('Test message'); |
| 231 | + |
| 232 | + $rendered = $this->renderer->render($this->stubLoader->load('issue'), $record); |
| 233 | + |
| 234 | + expect($rendered) |
| 235 | + ->toContain('**Environment:**'); |
| 236 | +}); |
| 237 | + |
| 238 | +test('issue template renders triage header with all fields', function () { |
| 239 | + $record = createLogRecord('Test message', [ |
| 240 | + 'request' => [ |
| 241 | + 'method' => 'GET', |
| 242 | + 'url' => 'https://example.com/test', |
| 243 | + 'headers' => ['X-Request-ID' => 'test123'], |
| 244 | + ], |
| 245 | + 'user' => ['id' => 456], |
| 246 | + 'environment' => ['APP_ENV' => 'testing'], |
| 247 | + ]); |
| 248 | + |
| 249 | + $rendered = $this->renderer->render($this->stubLoader->load('issue'), $record, 'sig123'); |
| 250 | + |
| 251 | + expect($rendered) |
| 252 | + ->toContain('## Triage Information') |
| 253 | + ->toContain('**Level:** ERROR') |
| 254 | + ->toContain('**Exception:**') |
| 255 | + ->toContain('**Signature:** sig123') |
| 256 | + ->toContain('**Timestamp:**') |
| 257 | + ->toContain('**Environment:** testing') |
| 258 | + ->toContain('**Route:** GET /test') |
| 259 | + ->toContain('**User:** User ID: 456') |
| 260 | + ->toContain('**Message:** Test message'); |
| 261 | +}); |
| 262 | + |
| 263 | +test('issue template wraps verbose sections in details blocks', function () { |
| 264 | + $record = createLogRecord('Test message', [ |
| 265 | + 'environment' => ['APP_ENV' => 'testing'], |
| 266 | + 'request' => ['method' => 'GET', 'url' => 'https://example.com'], |
| 267 | + 'user' => ['id' => 123], |
| 268 | + 'custom_context' => 'test', |
| 269 | + ], extra: ['extra_key' => 'extra_value']); |
| 270 | + |
| 271 | + $rendered = $this->renderer->render($this->stubLoader->load('issue'), $record); |
| 272 | + |
| 273 | + expect($rendered) |
| 274 | + ->toContain('<details>') |
| 275 | + ->toContain('<summary>🌍 Environment</summary>') |
| 276 | + ->toContain('<summary>📥 Request</summary>') |
| 277 | + ->toContain('<summary>👤 User Details</summary>') |
| 278 | + ->toContain('<summary>📦 Context</summary>') |
| 279 | + ->toContain('<summary>➕ Extra Data</summary>'); |
| 280 | +}); |
| 281 | + |
| 282 | +test('comment template always includes request section', function () { |
| 283 | + $record = createLogRecord('Test message', [ |
| 284 | + 'request' => [ |
| 285 | + 'method' => 'POST', |
| 286 | + 'url' => 'https://example.com/api', |
| 287 | + 'headers' => ['X-Request-ID' => 'req456'], |
| 288 | + ], |
| 289 | + ]); |
| 290 | + |
| 291 | + $rendered = $this->renderer->render($this->stubLoader->load('comment'), $record); |
| 292 | + |
| 293 | + expect($rendered) |
| 294 | + ->toContain('<summary>📥 Request</summary>') |
| 295 | + ->toContain('**Route:** POST /api'); |
| 296 | +}); |
| 297 | + |
| 298 | +test('comment template does not include environment section', function () { |
| 299 | + $record = createLogRecord('Test message', [ |
| 300 | + 'environment' => ['APP_ENV' => 'production'], |
| 301 | + ]); |
| 302 | + |
| 303 | + $rendered = $this->renderer->render($this->stubLoader->load('comment'), $record); |
| 304 | + |
| 305 | + expect($rendered) |
| 306 | + ->not->toContain('<summary>🌍 Environment</summary>') |
| 307 | + ->not->toContain('<!-- environment:start -->'); |
| 308 | +}); |
| 309 | + |
| 310 | +test('triage header renders correctly with missing optional data', function () { |
| 311 | + $record = createLogRecord('Test message'); |
| 312 | + |
| 313 | + $rendered = $this->renderer->render($this->stubLoader->load('issue'), $record, 'sig789'); |
| 314 | + |
| 315 | + expect($rendered) |
| 316 | + ->toContain('## Triage Information') |
| 317 | + ->toContain('**Level:** ERROR') |
| 318 | + ->toContain('**Signature:** sig789') |
| 319 | + ->toContain('**Timestamp:**') |
| 320 | + ->toContain('**Environment:**') |
| 321 | + ->toContain('**Route:**') |
| 322 | + ->toContain('**User:** Unauthenticated') |
| 323 | + ->toContain('**Message:** Test message'); |
| 324 | +}); |
0 commit comments