withoutMiddleware([ThrottleRequests::class, PreventRequestForgery::class]); } /** * @param array $overrides * @return array */ private function validPayload(array $overrides = []): array { return array_merge([ 'nome' => 'Maria Silva', 'email' => 'maria@example.com', 'telefone' => '(11) 98888-7777', 'tipo_evento' => 'Casamento', 'data_periodo' => 'novembro de 2027', 'cidade' => 'São Paulo', 'convidados' => '120', 'servico_interesse' => 'Planejamento completo', 'mensagem' => 'Queremos um casamento ao ar livre para 120 convidados.', 'privacidade' => '1', 'empresa' => '', ], $overrides); } public function test_utm_parameters_present_on_arrival_reach_the_briefing_email(): void { SiteSetting::instance(); Mail::fake(); $this->get('/briefing?utm_source=instagram&utm_medium=social&utm_campaign=lancamento-2026') ->assertOk(); $this->post(route('briefing.store'), $this->validPayload()) ->assertRedirect(route('briefing')) ->assertSessionHas('status', 'briefing-sent'); Mail::assertQueued(ContactBriefing::class, function (ContactBriefing $mail): bool { $origin = $mail->fields['Origem de marketing']; return $origin === 'origem: instagram | mídia: social | campanha: lancamento-2026'; }); } public function test_no_utm_or_referrer_still_submits_and_omits_the_origin(): void { SiteSetting::instance(); Mail::fake(); // No GET beforehand: posting cold, exactly like a direct/no-JS submission. $response = $this->post(route('briefing.store'), $this->validPayload()); $response ->assertRedirect(route('briefing')) ->assertSessionHas('status', 'briefing-sent'); Mail::assertQueued(ContactBriefing::class, function (ContactBriefing $mail): bool { return array_key_exists('Origem de marketing', $mail->fields) && $mail->fields['Origem de marketing'] === null; }); } public function test_referrer_only_visit_records_only_the_referring_site_as_origin(): void { SiteSetting::instance(); Mail::fake(); // The query string is a personalized campaign link (carries an // identifiable e-mail/subscriber id) — only the site identity may // be captured as marketing origin, never the query string itself. $this->withHeader('referer', 'https://mail.example.com/click?email=maria%40example.com&subscriber_id=42') ->get('/briefing') ->assertOk(); $this->post(route('briefing.store'), $this->validPayload()) ->assertRedirect(route('briefing')) ->assertSessionHas('status', 'briefing-sent'); Mail::assertQueued(ContactBriefing::class, function (ContactBriefing $mail): bool { return $mail->fields['Origem de marketing'] === 'https://mail.example.com'; }); } public function test_first_touch_wins_when_later_navigation_has_no_utm(): void { SiteSetting::instance(); Mail::fake(); $this->get('/briefing?utm_source=google&utm_medium=cpc') ->assertOk(); // Navigate elsewhere with no UTM parameters at all before submitting. $this->get('/servicos')->assertOk(); $this->get('/sobre')->assertOk(); $this->post(route('briefing.store'), $this->validPayload()) ->assertRedirect(route('briefing')) ->assertSessionHas('status', 'briefing-sent'); Mail::assertQueued(ContactBriefing::class, function (ContactBriefing $mail): bool { return $mail->fields['Origem de marketing'] === 'origem: google | mídia: cpc'; }); } public function test_first_touch_wins_when_later_navigation_carries_a_competing_utm(): void { SiteSetting::instance(); Mail::fake(); $this->get('/briefing?utm_source=google&utm_medium=cpc') ->assertOk(); // A second page load with its own, different UTM parameters must // not overwrite what was captured on first touch. $this->get('/briefing?utm_source=facebook&utm_medium=social') ->assertOk(); $this->post(route('briefing.store'), $this->validPayload()) ->assertRedirect(route('briefing')) ->assertSessionHas('status', 'briefing-sent'); Mail::assertQueued(ContactBriefing::class, function (ContactBriefing $mail): bool { return $mail->fields['Origem de marketing'] === 'origem: google | mídia: cpc'; }); } public function test_confirmation_email_never_exposes_the_marketing_origin(): void { $settings = SiteSetting::instance(); Mail::fake(); $this->get('/briefing?utm_source=instagram&utm_campaign=segredo-interno') ->assertOk(); $this->post(route('briefing.store'), $this->validPayload()) ->assertRedirect(route('briefing')) ->assertSessionHas('status', 'briefing-sent'); Mail::assertQueued(ContactBriefingConfirmation::class, function (ContactBriefingConfirmation $mail): bool { $rendered = $mail->render(); return ! str_contains($rendered, 'instagram') && ! str_contains($rendered, 'segredo-interno'); }); // The internal briefing mail is the only one carrying the origin. Mail::assertQueued(ContactBriefing::class, function (ContactBriefing $mail) use ($settings): bool { return $mail->hasTo($settings->email) && str_contains((string) $mail->fields['Origem de marketing'], 'segredo-interno'); }); } public function test_fail_open_when_stored_marketing_origin_is_corrupted(): void { SiteSetting::instance(); Mail::fake(); // Simulates the session value having been tampered with or // corrupted (e.g. by an unrelated bug) into something that is not // the array shape the controller expects. The acceptance // criterion is that this never blocks the submission. $response = $this->withSession([MarketingOrigin::SESSION_KEY => 'lixo']) ->post(route('briefing.store'), $this->validPayload()); $response ->assertRedirect(route('briefing')) ->assertSessionHas('status', 'briefing-sent'); Mail::assertQueued(ContactBriefing::class, function (ContactBriefing $mail): bool { return array_key_exists('Origem de marketing', $mail->fields) && $mail->fields['Origem de marketing'] === null; }); } public function test_hostile_marketing_origin_values_are_neutralised(): void { SiteSetting::instance(); Mail::fake(); $overlongCampaign = str_repeat('a', 300); $this->get('/briefing?'.http_build_query([ 'utm_source' => '', 'utm_campaign' => $overlongCampaign, ]))->assertOk(); $this->post(route('briefing.store'), $this->validPayload()) ->assertRedirect(route('briefing')) ->assertSessionHas('status', 'briefing-sent'); $capturedOrigin = null; Mail::assertQueued(ContactBriefing::class, function (ContactBriefing $mail) use (&$capturedOrigin): bool { $capturedOrigin = $mail->fields['Origem de marketing']; return true; }); self::assertIsString($capturedOrigin); self::assertStringNotContainsString('', $capturedOrigin); self::assertMatchesRegularExpression('/campanha: (a+)/', $capturedOrigin); preg_match('/campanha: (a+)/', $capturedOrigin, $matches); self::assertSame(100, mb_strlen($matches[1])); // Rendering into the HTML e-mail must not reintroduce markup either. Mail::assertQueued(ContactBriefing::class, function (ContactBriefing $mail): bool { $rendered = $mail->render(); return ! str_contains($rendered, ''); }); } public function test_technical_routes_do_not_consume_the_first_touch_slot(): void { $this->get('/sitemap.xml?utm_source=crawler&utm_medium=bot')->assertOk(); $this->get('/robots.txt?utm_source=crawler')->assertOk(); $this->assertNull(session(MarketingOrigin::SESSION_KEY)); $this->get('/?utm_source=instagram&utm_medium=perfil')->assertOk(); $this->assertSame('instagram', session(MarketingOrigin::SESSION_KEY)['utm_source'] ?? null); } }