Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
59.65% covered (warning)
59.65%
34 / 57
54.55% covered (warning)
54.55%
6 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
NuvemshopClient
59.65% covered (warning)
59.65%
34 / 57
54.55% covered (warning)
54.55%
6 / 11
74.89
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isMocked
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 canCallApi
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
 get
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 post
33.33% covered (danger)
33.33%
4 / 12
0.00% covered (danger)
0.00%
0 / 1
8.74
 put
66.67% covered (warning)
66.67%
4 / 6
0.00% covered (danger)
0.00%
0 / 1
3.33
 http
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 url
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 assertCanCallApi
50.00% covered (danger)
50.00%
4 / 8
0.00% covered (danger)
0.00%
0 / 1
8.12
 hasScope
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 delete
66.67% covered (warning)
66.67%
4 / 6
0.00% covered (danger)
0.00%
0 / 1
3.33
1<?php
2
3namespace App\Services\Nuvemshop;
4
5use App\Models\StoreIntegration;
6use Illuminate\Http\Client\PendingRequest;
7use Illuminate\Support\Facades\Http;
8use Illuminate\Support\Facades\Log;
9use RuntimeException;
10
11readonly class NuvemshopClient
12{
13    public function __construct(
14        private ?StoreIntegration $integration = null
15    ) {
16    }
17
18    public function isMocked(): bool
19    {
20        return (bool) config('services.nuvemshop.mock');
21    }
22
23    public function canCallApi(): bool
24    {
25        return ! $this->isMocked()
26            && $this->integration !== null
27            && filled($this->integration->external_store_id)
28            && filled($this->integration->access_token);
29    }
30
31    public function get(string $path, array $query = []): array
32    {
33        if (! $this->canCallApi()) {
34            Log::info('Nuvemshop GET ignorado: mock ativo ou integração sem credenciais.', compact('path', 'query'));
35
36            return [];
37        }
38
39        $response = $this->http()->get($this->url($path), $query);
40
41        if ($response->failed()) {
42            throw new RuntimeException('Falha ao consultar Nuvemshop: ' . $response->body());
43        }
44
45        return $response->json() ?? [];
46    }
47
48    public function post(string $path, array $payload = [], bool $allowMock = true): array
49    {
50        if (! $this->canCallApi()) {
51            if (! $allowMock) {
52                $this->assertCanCallApi();
53            }
54
55            Log::info('Nuvemshop POST ignorado: mock ativo ou integração sem credenciais.', compact('path', 'payload'));
56
57            return [
58                'mocked' => true,
59                'id' => 'mock-'.sha1($path.serialize($payload)),
60            ];
61        }
62
63        $response = $this->http()->post($this->url($path), $payload);
64
65        if ($response->failed()) {
66            throw new RuntimeException('Falha ao enviar dados à Nuvemshop: '.$response->body());
67        }
68
69        return $response->json() ?? [];
70    }
71
72    public function put(string $path, array $payload = []): array
73    {
74        if (! $this->canCallApi()) {
75            $this->assertCanCallApi();
76        }
77
78        $response = $this->http()->put($this->url($path), $payload);
79
80        if ($response->failed()) {
81            throw new RuntimeException('Falha ao atualizar dados na Nuvemshop: '.$response->body());
82        }
83
84        return $response->json() ?? [];
85    }
86
87    private function http(): PendingRequest
88    {
89        return Http::acceptJson()
90            ->asJson()
91            ->withToken($this->integration?->access_token)
92            ->withHeaders([
93                'User-Agent' => config('services.nuvemshop.user_agent'),
94            ])
95            ->timeout(30)
96            ->retry(2, 500);
97    }
98
99    private function url(string $path): string
100    {
101        $base = rtrim((string) config('services.nuvemshop.api_base_url'), '/');
102
103        return $base . '/' . ltrim($path, '/');
104    }
105
106    public function assertCanCallApi(): void
107    {
108        if ($this->isMocked()) {
109            throw new RuntimeException('NUVEMSHOP_MOCK=true. A chamada real para Nuvemshop está desativada.');
110        }
111
112        if (! $this->integration) {
113            throw new RuntimeException('Integração Nuvemshop não encontrada.');
114        }
115
116        if (blank($this->integration->external_store_id)) {
117            throw new RuntimeException('Integração Nuvemshop sem external_store_id/user_id.');
118        }
119
120        if (blank($this->integration->access_token)) {
121            throw new RuntimeException('Integração Nuvemshop sem access_token.');
122        }
123    }
124
125    public function hasScope(string $scope): bool
126    {
127        $scopes = $this->integration?->scopes ?? [];
128
129        return in_array($scope, $scopes, true);
130    }
131
132    public function delete(string $path): array
133    {
134        if (! $this->canCallApi()) {
135            $this->assertCanCallApi();
136        }
137
138        $response = $this->http()->delete($this->url($path));
139
140        if ($response->failed()) {
141            throw new RuntimeException('Falha ao excluir dados na Nuvemshop: '.$response->body());
142        }
143
144        return $response->json() ?? [];
145    }
146}