Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 109
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
RunNuvemshopE2ECommand
0.00% covered (danger)
0.00%
0 / 109
0.00% covered (danger)
0.00%
0 / 4
182
0.00% covered (danger)
0.00%
0 / 1
 handle
0.00% covered (danger)
0.00%
0 / 77
0.00% covered (danger)
0.00%
0 / 1
90
 resolveIntegration
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 createLocalInfluencer
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
2
 writeManifest
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace App\Console\Commands;
4
5use App\Enums\Status;
6use App\Enums\UserRole;
7use App\Models\Influencer;
8use App\Models\StoreIntegration;
9use App\Models\User;
10use App\Services\Nuvemshop\NuvemshopCouponService;
11use App\Services\Nuvemshop\NuvemshopOrderService;
12use App\Services\Nuvemshop\NuvemshopProductService;
13use App\Services\Nuvemshop\NuvemshopWebhookRegistrationService;
14use Illuminate\Console\Command;
15use Illuminate\Support\Facades\File;
16use Illuminate\Support\Str;
17use Throwable;
18
19class RunNuvemshopE2ECommand extends Command
20{
21    protected $signature = 'orvox:test:nuvemshop-e2e
22        {--integration-id= : ID da store_integrations real}
23        {--coupon= : Código do cupom. Se vazio, gera ORVOXTESTE...}
24        {--discount=1 : Percentual de desconto do cupom}
25        {--product-price=1.00 : Preço do produto de teste}
26        {--webhook-url= : URL HTTPS do ngrok para cadastrar webhooks}
27        {--simulate-webhook : Simula recebimento de pedido pago no Orvox sem compra real}
28        {--cleanup-now : Exclui produto/cupom de teste ao final do comando}';
29
30    protected $description = 'Prepara e valida um fluxo E2E real da Nuvemshop: influencer, cupom, produto teste, webhooks e simulação opcional de pedido.';
31
32    private string $manifestPath = 'storage/app/orvox-nuvemshop-e2e-manifest.json';
33
34    public function handle(
35        NuvemshopCouponService $couponService,
36        NuvemshopProductService $productService,
37        NuvemshopWebhookRegistrationService $webhookService,
38        NuvemshopOrderService $orderService,
39    ): int {
40        if ((bool) config('services.nuvemshop.mock')) {
41            $this->error('NUVEMSHOP_MOCK=true. Para E2E real, deixe NUVEMSHOP_MOCK=false.');
42            return self::FAILURE;
43        }
44
45        $integration = $this->resolveIntegration();
46
47        if (! $integration) {
48            $this->error('Integração Nuvemshop não encontrada.');
49            return self::FAILURE;
50        }
51
52        $couponCode = strtoupper((string) ($this->option('coupon') ?: 'ORVOXTESTE'.now()->format('His')));
53        $created = [
54            'started_at' => now()->toISOString(),
55            'integration_id' => $integration->id,
56            'external_store_id' => $integration->external_store_id,
57            'coupon_code' => $couponCode,
58            'local' => [],
59            'nuvemshop' => [],
60        ];
61
62        try {
63            $influencer = $this->createLocalInfluencer($integration, $couponCode);
64            $created['local']['influencer_id'] = $influencer->id;
65            $created['local']['user_id'] = $influencer->user_id;
66
67            $coupon = $couponService->createForInfluencer(
68                $influencer,
69                $integration,
70                $couponCode,
71                (float) $this->option('discount')
72            );
73            $created['local']['coupon_id'] = $coupon->id;
74            $created['nuvemshop']['coupon_id'] = $coupon->external_coupon_id;
75
76            $product = $productService->createTestProduct($integration, [
77                'name' => '[ORVOX TESTE E2E] '.$couponCode,
78                'price' => (float) $this->option('product-price'),
79            ]);
80            $created['nuvemshop']['product_id'] = data_get($product, 'id');
81            $created['nuvemshop']['product_checkout_hint'] = $productService->checkoutHint($product);
82
83            if ($webhookUrl = $this->option('webhook-url')) {
84                $created['nuvemshop']['webhooks'] = $webhookService->registerDefaultOrderWebhooks($integration, (string) $webhookUrl);
85            }
86
87            if ($this->option('simulate-webhook')) {
88                $payload = [
89                    'id' => 'orvox-e2e-'.Str::lower(Str::random(8)),
90                    'number' => 'E2E-'.random_int(1000, 9999),
91                    'status' => 'closed',
92                    'payment_status' => 'paid',
93                    'subtotal' => (float) $this->option('product-price'),
94                    'discount' => round(((float) $this->option('product-price')) * ((float) $this->option('discount') / 100), 2),
95                    'shipping_cost_owner' => 0,
96                    'total' => (float) $this->option('product-price'),
97                    'coupon' => [['code' => $couponCode]],
98                    'created_at' => now()->toIso8601String(),
99                    'paid_at' => now()->toIso8601String(),
100                    'products' => [[
101                        'id' => data_get($product, 'id'),
102                        'product_id' => data_get($product, 'id'),
103                        'variant_id' => data_get($product, 'variants.0.id'),
104                        'name' => '[ORVOX TESTE E2E] '.$couponCode,
105                        'sku' => 'ORVOX-E2E',
106                        'quantity' => 1,
107                        'price' => (float) $this->option('product-price'),
108                    ]],
109                ];
110
111                $result = $orderService->upsertOrderFromPayload($integration, $payload);
112                $created['local']['simulated_order_id'] = data_get($result, 'order.id');
113            }
114
115            $this->writeManifest($created);
116
117            $this->info('Cenário E2E preparado com sucesso.');
118            $this->line('Influencer local ID: '.$created['local']['influencer_id']);
119            $this->line('Cupom: '.$couponCode.' | Nuvemshop coupon ID: '.$created['nuvemshop']['coupon_id']);
120            $this->line('Produto Nuvemshop ID: '.$created['nuvemshop']['product_id']);
121            $this->line('Hint/URL produto: '.$created['nuvemshop']['product_checkout_hint']);
122
123            if (! $this->option('simulate-webhook')) {
124                $this->warn('Faça uma compra manual usando o cupom acima e confirme se o webhook chegou em /api/webhooks/nuvemshop/{integration}.');
125            }
126
127            if ($this->option('cleanup-now')) {
128                $this->call('orvox:test:nuvemshop-cleanup');
129            } else {
130                $this->warn('Depois do teste, rode: php artisan orvox:test:nuvemshop-cleanup');
131            }
132
133            return self::SUCCESS;
134        } catch (Throwable $exception) {
135            $this->writeManifest($created);
136            $this->error($exception->getMessage());
137            $this->warn('Manifest salvo para cleanup parcial: '.$this->manifestPath);
138            return self::FAILURE;
139        }
140    }
141
142    private function resolveIntegration(): ?StoreIntegration
143    {
144        $query = StoreIntegration::query()
145            ->whereHas('provider', fn ($q) => $q->where('slug', 'nuvemshop'));
146
147        if ($id = $this->option('integration-id')) {
148            $query->whereKey($id);
149        }
150
151        return $query->latest()->first();
152    }
153
154    private function createLocalInfluencer(StoreIntegration $integration, string $couponCode): Influencer
155    {
156        $email = 'e2e+'.strtolower($couponCode).'@orvox.test';
157
158        $user = User::updateOrCreate(
159            ['email' => $email],
160            [
161                'tenant_id' => $integration->tenant_id,
162                'store_id' => $integration->store_id,
163                'name' => 'Influencer E2E '.$couponCode,
164                'password' => 'password',
165                'role' => UserRole::Influencer,
166                'status' => Status::Active,
167            ]
168        );
169
170        return Influencer::updateOrCreate(
171            ['store_id' => $integration->store_id, 'email' => $email],
172            [
173                'tenant_id' => $integration->tenant_id,
174                'user_id' => $user->id,
175                'name' => 'Influencer E2E '.$couponCode,
176                'instagram' => '@orvoxe2e',
177                'pix_key' => 'e2e@orvox.test',
178                'base_commission_percentage' => 8,
179                'status' => Status::Active,
180                'internal_notes' => 'Criado por orvox:test:nuvemshop-e2e.',
181            ]
182        );
183    }
184
185    private function writeManifest(array $payload): void
186    {
187        File::ensureDirectoryExists(dirname(base_path($this->manifestPath)));
188        File::put(base_path($this->manifestPath), json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
189    }
190}