• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

marscoin / martianrepublic / 23749891416

30 Mar 2026 02:25PM UTC coverage: 8.467% (+3.2%) from 5.241%
23749891416

push

github

Martian Congress
fix: resolve all 10 PHPStan errors + add 37 new tests

Fixes:
- DashboardController: undefined $user/$citizen in showReports/showCamera
- AiHelperController: env() → config() for cached config safety
- BADS models: add return type hints so Larastan resolves relations
- Ballots model: add return type hints for proposal/user relations
- ErrorTriageMail: fix regex delimiter and add unicode flag
- services.php: add openrouter config block

New test suites (37 tests, 97 assertions):
- AcademyTest: 10 article page loads + 404 handling
- ForumTest: categories, threads, posts, ordering, proposal linkage
- CongressTest: ballot relations, pendingForUser, controller methods
- InstrumentTest: chain of trust, calibration, attestations, soft delete
- AiHelperTest: validation, error triage mail, severity extraction

Total: 79 tests, 161 assertions, 0 PHPStan errors

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

14 of 21 new or added lines in 8 files covered. (66.67%)

1257 existing lines in 18 files now uncovered.

445 of 5256 relevant lines covered (8.47%)

1.06 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

19.05
/app/Exceptions/Handler.php
1
<?php
2

3
namespace App\Exceptions;
4

5
use App\Jobs\ErrorTriageJob;
6
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
7
use Illuminate\Support\Facades\Cache;
8
use Throwable;
9

10
class Handler extends ExceptionHandler
11
{
12
    /**
13
     * A list of the exception types that are not reported.
14
     *
15
     * @var array<int, class-string<Throwable>>
16
     */
17
    protected $dontReport = [
18
        //
19
    ];
20

21
    /**
22
     * A list of the inputs that are never flashed for validation exceptions.
23
     *
24
     * @var array<int, string>
25
     */
26
    protected $dontFlash = [
27
        'current_password',
28
        'password',
29
        'password_confirmation',
30
    ];
31

32
    /**
33
     * Register the exception handling callbacks for the application.
34
     */
35
    public function register(): void
78✔
36
    {
37
        $this->reportable(function (Throwable $e) {
78✔
38
            // Sentry (existing)
39
            if (app()->bound('sentry')) {
5✔
UNCOV
40
                app('sentry')->captureException($e);
×
41
            }
42

43
            // AI Error Triage — only for 500-level exceptions
44
            $this->triageError($e);
5✔
45
        });
78✔
46
    }
47

48
    /**
49
     * Dispatch AI triage for server errors, with deduplication.
50
     */
51
    private function triageError(Throwable $e): void
5✔
52
    {
53
        // Skip if no API key configured
54
        if (! config('services.error_triage.openrouter_key')) {
5✔
55
            return;
5✔
56
        }
57

58
        // Skip non-500 exceptions (validation, auth, 404, etc.)
UNCOV
59
        $skipExceptions = [
×
UNCOV
60
            \Illuminate\Auth\AuthenticationException::class,
×
UNCOV
61
            \Illuminate\Auth\Access\AuthorizationException::class,
×
UNCOV
62
            \Illuminate\Database\Eloquent\ModelNotFoundException::class,
×
UNCOV
63
            \Illuminate\Validation\ValidationException::class,
×
UNCOV
64
            \Symfony\Component\HttpKernel\Exception\HttpException::class,
×
UNCOV
65
            \Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class,
×
UNCOV
66
            \Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException::class,
×
UNCOV
67
        ];
×
68

UNCOV
69
        foreach ($skipExceptions as $type) {
×
UNCOV
70
            if ($e instanceof $type) {
×
UNCOV
71
                return;
×
72
            }
73
        }
74

75
        // Dedup: same exception class + file + line = same error
UNCOV
76
        $fingerprint = md5(get_class($e) . $e->getFile() . $e->getLine());
×
UNCOV
77
        $cacheKey = 'error_triage:' . $fingerprint;
×
UNCOV
78
        $cooldown = (int) config('services.error_triage.cooldown_minutes', 15);
×
79

UNCOV
80
        if (Cache::has($cacheKey)) {
×
UNCOV
81
            return;
×
82
        }
83

UNCOV
84
        Cache::put($cacheKey, true, now()->addMinutes($cooldown));
×
85

86
        // Gather request context
UNCOV
87
        $request = request();
×
UNCOV
88
        $url = $request ? $request->fullUrl() : 'CLI';
×
UNCOV
89
        $method = $request ? $request->method() : 'CLI';
×
UNCOV
90
        $userId = $request?->user()?->id;
×
91

UNCOV
92
        ErrorTriageJob::dispatch(
×
UNCOV
93
            exceptionClass: get_class($e),
×
UNCOV
94
            message: $e->getMessage(),
×
UNCOV
95
            file: $e->getFile(),
×
UNCOV
96
            line: $e->getLine(),
×
UNCOV
97
            trace: $e->getTraceAsString(),
×
UNCOV
98
            url: $url,
×
UNCOV
99
            method: $method,
×
UNCOV
100
            userId: $userId ? (string) $userId : null,
×
UNCOV
101
            occurredAt: now()->toDateTimeString(),
×
UNCOV
102
        );
×
103
    }
104
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc