fix: SonarQube - SentryService, RgpdService, FacturePdf, DevisPdf
SentryService : - PATH_PROJECTS, PATH_ISSUES constantes (9 occurrences) - request() 5->3 returns via executeRequest() RgpdService : - verifyCode() 5->2 returns (logique booleenne unifiee) FacturePdf : - Suppression appendRib() et displayHmac() inutilisees - Catch vide commente (CGV best-effort) DevisPdf : - Catch vide commente (CGV best-effort) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -204,6 +204,7 @@ class DevisPdf extends Fpdi
|
||||
|
||||
@unlink($tmpCgv);
|
||||
} catch (\Throwable) {
|
||||
// CGV generation is best-effort
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -214,50 +214,10 @@ class FacturePdf extends Fpdi
|
||||
|
||||
@unlink($tmpCgv);
|
||||
} catch (\Throwable) { // @codeCoverageIgnore
|
||||
// CGV generation is best-effort
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Importe les pages de public/rib.pdf apres les CGV.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
private function appendRib(): void
|
||||
{
|
||||
$ribPath = $this->kernel->getProjectDir().'/public/rib.pdf';
|
||||
if (!file_exists($ribPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$pageCount = $this->setSourceFile($ribPath);
|
||||
|
||||
for ($i = 1; $i <= $pageCount; ++$i) {
|
||||
$tpl = $this->importPage($i);
|
||||
$size = $this->getTemplateSize($tpl);
|
||||
$this->AddPage($size['orientation'] ?? 'P', [$size['width'], $size['height']]);
|
||||
$this->useTemplate($tpl);
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
private function displayHmac(): void
|
||||
{
|
||||
$this->Ln(6);
|
||||
$this->SetFont('Arial', '', 6);
|
||||
$this->SetTextColor(150, 150, 150);
|
||||
$this->Cell(190, 4, $this->enc('Signature HMAC-SHA256 : '.$this->facture->getHmac()), 0, 1, 'L');
|
||||
$this->SetTextColor(0, 0, 0);
|
||||
}
|
||||
|
||||
private function displayQrCode(): void
|
||||
{
|
||||
if ('' === $this->qrBase64) {
|
||||
|
||||
@@ -99,28 +99,16 @@ class RgpdService
|
||||
}
|
||||
|
||||
$data = json_decode(file_get_contents($codePath), true);
|
||||
if (null === $data) {
|
||||
$isValid = null !== $data
|
||||
&& time() <= ($data['expires'] ?? 0)
|
||||
&& $code === ($data['code'] ?? '');
|
||||
|
||||
// Supprime le fichier si invalide (expire/corrompu) ou valide (usage unique)
|
||||
if (null === $data || time() > ($data['expires'] ?? 0) || $isValid) {
|
||||
@unlink($codePath);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Expire ?
|
||||
if (time() > ($data['expires'] ?? 0)) {
|
||||
@unlink($codePath);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Code correct ?
|
||||
if ($code !== ($data['code'] ?? '')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Supprime le fichier (usage unique)
|
||||
@unlink($codePath);
|
||||
|
||||
return true;
|
||||
return $isValid;
|
||||
}
|
||||
|
||||
private function getCodeFilePath(string $email, string $ip, string $type): string
|
||||
|
||||
@@ -13,6 +13,9 @@ use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
*/
|
||||
class SentryService
|
||||
{
|
||||
private const PATH_PROJECTS = '/projects/';
|
||||
private const PATH_ISSUES = '/issues/';
|
||||
|
||||
public function __construct(
|
||||
private HttpClientInterface $httpClient,
|
||||
private LoggerInterface $logger,
|
||||
@@ -36,14 +39,14 @@ class SentryService
|
||||
{
|
||||
$org = $this->resolveOrg($org);
|
||||
|
||||
return $this->request('POST', '/teams/'.$org.'/'.$teamSlug.'/projects/', ['name' => $name]);
|
||||
return $this->request('POST', '/teams/'.$org.'/'.$teamSlug.self::PATH_PROJECTS, ['name' => $name]);
|
||||
}
|
||||
|
||||
public function deleteProject(string $projectSlug, string $org = ''): bool
|
||||
{
|
||||
$org = $this->resolveOrg($org);
|
||||
|
||||
return null !== $this->request('DELETE', '/projects/'.$org.'/'.$projectSlug.'/');
|
||||
return null !== $this->request('DELETE', self::PATH_PROJECTS.$org.'/'.$projectSlug.'/');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,7 +55,7 @@ class SentryService
|
||||
public function getProjectDsn(string $projectSlug, string $org = ''): ?string
|
||||
{
|
||||
$org = $this->resolveOrg($org);
|
||||
$result = $this->request('GET', '/projects/'.$org.'/'.$projectSlug.'/keys/');
|
||||
$result = $this->request('GET', self::PATH_PROJECTS.$org.'/'.$projectSlug.'/keys/');
|
||||
|
||||
if (null === $result || !isset($result[0]['dsn']['public'])) {
|
||||
return null;
|
||||
@@ -71,7 +74,7 @@ class SentryService
|
||||
$org = $this->resolveOrg($org);
|
||||
|
||||
/* @var list<array<string, mixed>> */
|
||||
return $this->request('GET', '/organizations/'.$org.'/projects/') ?? [];
|
||||
return $this->request('GET', '/organizations/'.$org.self::PATH_PROJECTS) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,7 +85,7 @@ class SentryService
|
||||
public function listProjectErrors(string $projectSlug, ?string $query = null, string $org = ''): array
|
||||
{
|
||||
$org = $this->resolveOrg($org);
|
||||
$path = '/projects/'.$org.'/'.$projectSlug.'/issues/';
|
||||
$path = self::PATH_PROJECTS.$org.'/'.$projectSlug.self::PATH_ISSUES;
|
||||
|
||||
if (null !== $query && '' !== $query) {
|
||||
$path .= '?query='.urlencode($query);
|
||||
@@ -99,7 +102,7 @@ class SentryService
|
||||
*/
|
||||
public function getIssueDetails(string $issueId): ?array
|
||||
{
|
||||
return $this->request('GET', '/issues/'.$issueId.'/');
|
||||
return $this->request('GET', self::PATH_ISSUES.$issueId.'/');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,7 +110,7 @@ class SentryService
|
||||
*/
|
||||
public function resolveIssue(string $issueId): bool
|
||||
{
|
||||
return null !== $this->request('PUT', '/issues/'.$issueId.'/', ['status' => 'resolved']);
|
||||
return null !== $this->request('PUT', self::PATH_ISSUES.$issueId.'/', ['status' => 'resolved']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,7 +118,7 @@ class SentryService
|
||||
*/
|
||||
public function deleteIssue(string $issueId): bool
|
||||
{
|
||||
return null !== $this->request('DELETE', '/issues/'.$issueId.'/');
|
||||
return null !== $this->request('DELETE', self::PATH_ISSUES.$issueId.'/');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,44 +135,38 @@ class SentryService
|
||||
}
|
||||
|
||||
try {
|
||||
$options = [
|
||||
'headers' => [
|
||||
'Authorization' => 'Bearer '.$this->authToken,
|
||||
'Content-Type' => 'application/json',
|
||||
],
|
||||
];
|
||||
|
||||
if (null !== $body) {
|
||||
$options['json'] = $body;
|
||||
}
|
||||
|
||||
$response = $this->httpClient->request($method, rtrim($this->apiUrl, '/').$path, $options);
|
||||
$statusCode = $response->getStatusCode();
|
||||
|
||||
if ($statusCode >= 400) {
|
||||
$this->logger->error('SentryService: HTTP '.$statusCode.' '.$method.' '.$path, [
|
||||
'body' => $response->getContent(false),
|
||||
]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// DELETE retourne 204 sans body
|
||||
if (204 === $statusCode || '' === $response->getContent(false)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $response->toArray();
|
||||
return $this->executeRequest($method, $path, $body);
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error('SentryService: '.$e->getMessage(), [
|
||||
'method' => $method,
|
||||
'path' => $path,
|
||||
]);
|
||||
$this->logger->error('SentryService: '.$e->getMessage(), ['method' => $method, 'path' => $path]);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed>|null $body
|
||||
*
|
||||
* @return array<int|string, mixed>|null
|
||||
*/
|
||||
private function executeRequest(string $method, string $path, ?array $body): ?array
|
||||
{
|
||||
$options = ['headers' => ['Authorization' => 'Bearer '.$this->authToken, 'Content-Type' => 'application/json']];
|
||||
if (null !== $body) {
|
||||
$options['json'] = $body;
|
||||
}
|
||||
|
||||
$response = $this->httpClient->request($method, rtrim($this->apiUrl, '/').$path, $options);
|
||||
$statusCode = $response->getStatusCode();
|
||||
|
||||
if ($statusCode >= 400) {
|
||||
$this->logger->error('SentryService: HTTP '.$statusCode.' '.$method.' '.$path, ['body' => $response->getContent(false)]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return (204 === $statusCode || '' === $response->getContent(false)) ? [] : $response->toArray();
|
||||
}
|
||||
|
||||
private function resolveOrg(string $org): string
|
||||
{
|
||||
return '' !== $org ? $org : $this->defaultOrg;
|
||||
|
||||
@@ -341,69 +341,4 @@ class FacturePdfTest extends TestCase
|
||||
$this->assertStringStartsWith('%PDF', $output);
|
||||
}
|
||||
|
||||
public function testDisplayHmacViaReflection(): void
|
||||
{
|
||||
// displayHmac() is a private method that is never called in generate().
|
||||
// Call it via reflection to ensure method-level coverage.
|
||||
$facture = $this->makeFacture('04/2026-00015');
|
||||
$facture->setTotalHt('10.00');
|
||||
$facture->setTotalTva('0.00');
|
||||
$facture->setTotalTtc('10.00');
|
||||
|
||||
$pdf = new FacturePdf($this->kernel, $facture);
|
||||
$pdf->generate(); // initialises the PDF with a page
|
||||
|
||||
$ref = new \ReflectionMethod(FacturePdf::class, 'displayHmac');
|
||||
$ref->setAccessible(true);
|
||||
$ref->invoke($pdf);
|
||||
|
||||
$output = $pdf->Output('S');
|
||||
$this->assertStringStartsWith('%PDF', $output);
|
||||
}
|
||||
|
||||
public function testAppendRibViaReflectionWhenNoRibFile(): void
|
||||
{
|
||||
// appendRib() is a private method that is never called in generate().
|
||||
// With no rib.pdf file present, it should return early.
|
||||
$facture = $this->makeFacture('04/2026-00016');
|
||||
|
||||
$pdf = new FacturePdf($this->kernel, $facture);
|
||||
$pdf->generate();
|
||||
|
||||
$ref = new \ReflectionMethod(FacturePdf::class, 'appendRib');
|
||||
$ref->setAccessible(true);
|
||||
$ref->invoke($pdf); // no rib.pdf -> returns immediately
|
||||
|
||||
$output = $pdf->Output('S');
|
||||
$this->assertStringStartsWith('%PDF', $output);
|
||||
}
|
||||
|
||||
public function testAppendRibViaReflectionWithRibFile(): void
|
||||
{
|
||||
// appendRib() with a valid rib.pdf file exercises the file-import path.
|
||||
$ribDir = $this->projectDir.'/public';
|
||||
if (!is_dir($ribDir)) {
|
||||
mkdir($ribDir, 0775, true);
|
||||
}
|
||||
|
||||
$miniPdf = new \setasign\Fpdi\Fpdi();
|
||||
$miniPdf->AddPage();
|
||||
$ribPath = $ribDir.'/rib.pdf';
|
||||
$miniPdf->Output('F', $ribPath);
|
||||
|
||||
$facture = $this->makeFacture('04/2026-00017');
|
||||
$facture->setTotalHt('20.00');
|
||||
$facture->setTotalTva('0.00');
|
||||
$facture->setTotalTtc('20.00');
|
||||
|
||||
$pdf = new FacturePdf($this->kernel, $facture);
|
||||
$pdf->generate();
|
||||
|
||||
$ref = new \ReflectionMethod(FacturePdf::class, 'appendRib');
|
||||
$ref->setAccessible(true);
|
||||
$ref->invoke($pdf);
|
||||
|
||||
$output = $pdf->Output('S');
|
||||
$this->assertStringStartsWith('%PDF', $output);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user