STEP 1~7 (거부 기능): - 쿠키 배너에 「동의하지 않고 계속하기」 거부 버튼 추가 (intent=reject) - is_rejected/rejected_at 컬럼 + ConsentAction Enum 3분기 (동의/철회/거부) - 관리자 동의 이력에 [거부] 필터 + amber 배지 STEP A~F (토글 재설계): - 필수 카테고리를 동의 축에서 완전 제외 (status/history 미저장) — ePrivacy Art.5(3) 면제 - 마이페이지 표→토글 전환: 필수=「항상 켜짐」 잠금, 선택=on/off 스위치, 동의일 제거, 「모두 켜기」 바 - 거부 토스트 문구 정정: consent.granted → rejected_saved (배너 + 컨트롤러 응답) 테스트: 백엔드 201/201, 프론트 288/288, 시나리오 커버리지 audit 통과 Chrome MCP 실측: 토글 grant/revoke/모두켜기 + 배너 거부 토스트 + 거부 직후 마이페이지 표기 확인
553 lines
23 KiB
PHP
553 lines
23 KiB
PHP
<?php
|
|
|
|
namespace Plugins\Sirsoft\Gdpr\Tests\Unit\Services;
|
|
|
|
use App\Models\User;
|
|
use App\Services\PluginSettingsService;
|
|
use Plugins\Sirsoft\Gdpr\Models\GdprUserConsent;
|
|
use Plugins\Sirsoft\Gdpr\Models\GdprUserConsentHistory;
|
|
use Plugins\Sirsoft\Gdpr\Repositories\GdprUserConsentHistoryRepository;
|
|
use Plugins\Sirsoft\Gdpr\Repositories\GdprUserConsentRepository;
|
|
use Plugins\Sirsoft\Gdpr\Services\GdprConsentService;
|
|
use Plugins\Sirsoft\Gdpr\Tests\PluginTestCase;
|
|
|
|
class GdprConsentServiceTest extends PluginTestCase
|
|
{
|
|
private GdprConsentService $service;
|
|
|
|
/** @var PluginSettingsService&\PHPUnit\Framework\MockObject\MockObject */
|
|
private PluginSettingsService $pluginSettings;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
$this->pluginSettings = $this->createMock(PluginSettingsService::class);
|
|
// 정책 버전은 gdpr_policy_versions 테이블이 SSoT (마이그레이션 시 initial 행 자동 시드 → v1).
|
|
// pluginSettings 의 cookie_policy_version 은 더 이상 출처가 아니지만 다른 키 호출 호환을 위해 Mock 유지.
|
|
$this->pluginSettings->method('get')->willReturnCallback(
|
|
fn (string $id, string $key, mixed $default = null) => $default
|
|
);
|
|
|
|
$this->service = new GdprConsentService(
|
|
new GdprUserConsentRepository(),
|
|
new GdprUserConsentHistoryRepository(),
|
|
$this->pluginSettings,
|
|
new \Plugins\Sirsoft\Gdpr\Services\CookieCategoryService($this->pluginSettings),
|
|
);
|
|
}
|
|
|
|
public function test_update_consent_creates_status_and_history_for_member(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
|
|
$this->service->updateConsent($user->id, null, 'cookie_analytics', true, 'banner');
|
|
|
|
$this->assertDatabaseHas('gdpr_user_consents', [
|
|
'user_id' => $user->id,
|
|
'consent_key' => 'cookie_analytics',
|
|
'is_consented' => true,
|
|
'consent_count' => 1,
|
|
'last_source' => 'banner',
|
|
'policy_version' => '1',
|
|
]);
|
|
|
|
$this->assertDatabaseHas('gdpr_user_consent_histories', [
|
|
'user_id' => $user->id,
|
|
'consent_key' => 'cookie_analytics',
|
|
'action' => 'granted',
|
|
'source' => 'banner',
|
|
'policy_version' => '1',
|
|
]);
|
|
}
|
|
|
|
public function test_update_consent_records_history_only_for_guest(): void
|
|
{
|
|
$sessionId = 'guest-session-abc';
|
|
|
|
$this->service->updateConsent(null, $sessionId, 'cookie_marketing', true, 'banner');
|
|
|
|
$this->assertDatabaseCount('gdpr_user_consents', 0);
|
|
$this->assertDatabaseHas('gdpr_user_consent_histories', [
|
|
'user_id' => null,
|
|
'session_id' => $sessionId,
|
|
'consent_key' => 'cookie_marketing',
|
|
'action' => 'granted',
|
|
]);
|
|
}
|
|
|
|
public function test_update_consent_is_noop_when_state_unchanged(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
|
|
$this->service->updateConsent($user->id, null, 'cookie_analytics', true, 'banner');
|
|
$this->service->updateConsent($user->id, null, 'cookie_analytics', true, 'banner');
|
|
|
|
$this->assertSame(1, GdprUserConsent::where('user_id', $user->id)->count());
|
|
$this->assertSame(1, GdprUserConsentHistory::where('user_id', $user->id)->count());
|
|
|
|
$consent = GdprUserConsent::where('user_id', $user->id)->first();
|
|
$this->assertSame(1, $consent->consent_count);
|
|
}
|
|
|
|
public function test_revoke_appends_history_and_flips_status(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
|
|
$this->service->updateConsent($user->id, null, 'cookie_analytics', true, 'banner');
|
|
$this->service->updateConsent($user->id, null, 'cookie_analytics', false, 'mypage');
|
|
|
|
$consent = GdprUserConsent::where('user_id', $user->id)->first();
|
|
$this->assertFalse($consent->is_consented);
|
|
$this->assertNotNull($consent->revoked_at);
|
|
$this->assertSame('mypage', $consent->last_source);
|
|
|
|
$this->assertSame(2, GdprUserConsentHistory::where('user_id', $user->id)->count());
|
|
$this->assertDatabaseHas('gdpr_user_consent_histories', [
|
|
'user_id' => $user->id,
|
|
'action' => 'revoked',
|
|
'source' => 'mypage',
|
|
]);
|
|
}
|
|
|
|
public function test_consent_count_increments_on_re_grant(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
|
|
$this->service->updateConsent($user->id, null, 'cookie_analytics', true, 'banner');
|
|
$this->service->updateConsent($user->id, null, 'cookie_analytics', false, 'mypage');
|
|
$this->service->updateConsent($user->id, null, 'cookie_analytics', true, 'mypage');
|
|
|
|
$consent = GdprUserConsent::where('user_id', $user->id)->first();
|
|
$this->assertTrue($consent->is_consented);
|
|
$this->assertSame(2, $consent->consent_count);
|
|
}
|
|
|
|
public function test_revoke_all_on_withdraw_revokes_only_active_consents(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
|
|
$this->service->updateConsent($user->id, null, 'cookie_analytics', true, 'banner');
|
|
$this->service->updateConsent($user->id, null, 'cookie_marketing', true, 'banner');
|
|
$this->service->updateConsent($user->id, null, 'cookie_marketing', false, 'mypage');
|
|
|
|
$this->service->revokeAllOnWithdraw($user->id);
|
|
|
|
$analytics = GdprUserConsent::where('user_id', $user->id)
|
|
->where('consent_key', 'cookie_analytics')->first();
|
|
$marketing = GdprUserConsent::where('user_id', $user->id)
|
|
->where('consent_key', 'cookie_marketing')->first();
|
|
|
|
$this->assertFalse($analytics->is_consented);
|
|
$this->assertSame('withdraw', $analytics->last_source);
|
|
$this->assertFalse($marketing->is_consented);
|
|
}
|
|
|
|
public function test_purge_on_user_delete_removes_status_and_anonymizes_history(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
|
|
$this->service->updateConsent($user->id, null, 'cookie_analytics', true, 'banner');
|
|
$this->service->updateConsent($user->id, null, 'cookie_marketing', true, 'banner');
|
|
|
|
$userId = $user->id;
|
|
|
|
$this->service->purgeOnUserDelete($userId);
|
|
|
|
$this->assertDatabaseCount('gdpr_user_consents', 0);
|
|
$this->assertSame(2, GdprUserConsentHistory::count());
|
|
$this->assertSame(0, GdprUserConsentHistory::where('user_id', $userId)->count());
|
|
$this->assertSame(2, GdprUserConsentHistory::whereNull('user_id')->count());
|
|
}
|
|
|
|
public function test_needs_renewal_detects_outdated_policy_version(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
|
|
$this->service->updateConsent($user->id, null, 'cookie_analytics', true, 'banner');
|
|
|
|
// 정책 버전이 v2 로 bump — gdpr_policy_versions 에 새 row INSERT
|
|
\Plugins\Sirsoft\Gdpr\Models\GdprPolicyVersion::create([
|
|
'version' => 2,
|
|
'change_type' => \Plugins\Sirsoft\Gdpr\Enums\GdprPolicyChangeType::Material->value,
|
|
'snapshot' => [],
|
|
]);
|
|
|
|
$this->assertTrue($this->service->needsRenewal($user->id));
|
|
}
|
|
|
|
public function test_needs_renewal_false_when_versions_match(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
|
|
$this->service->updateConsent($user->id, null, 'cookie_analytics', true, 'banner');
|
|
|
|
$this->assertFalse($this->service->needsRenewal($user->id));
|
|
}
|
|
|
|
public function test_get_active_consents_excludes_revoked(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
|
|
$this->service->updateConsent($user->id, null, 'cookie_analytics', true, 'banner');
|
|
$this->service->updateConsent($user->id, null, 'cookie_marketing', true, 'banner');
|
|
$this->service->updateConsent($user->id, null, 'cookie_marketing', false, 'mypage');
|
|
|
|
$active = $this->service->getActiveConsents($user->id);
|
|
|
|
$this->assertCount(1, $active);
|
|
$this->assertSame('cookie_analytics', $active->first()->consent_key);
|
|
}
|
|
|
|
/**
|
|
* v1.5.0 자동 차단 엔진 SSoT 회귀 가드:
|
|
* 회원의 카테고리별 동의 상태가 cookie_ 접두사 제거된 카테고리 키로 매핑되어야 함.
|
|
*
|
|
* 이슈 #430 후속 — 필수 카테고리는 동의 축에서 제외되어 status 에 저장되지 않으므로
|
|
* 이 맵에도 등장하지 않는다. 자동 차단 엔진(blocker.ts)은 necessary 를 맵 유무와
|
|
* 무관하게 항상 허용하므로(category==='necessary' → true) 필수 부재가 안전하다.
|
|
*/
|
|
public function test_get_current_cookie_consents_returns_category_map_for_member(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
|
|
$this->service->updateConsent($user->id, null, 'cookie_necessary', true, 'banner');
|
|
$this->service->updateConsent($user->id, null, 'cookie_analytics', true, 'banner');
|
|
$this->service->updateConsent($user->id, null, 'cookie_marketing', true, 'banner');
|
|
$this->service->updateConsent($user->id, null, 'cookie_marketing', false, 'mypage');
|
|
|
|
$consents = $this->service->getCurrentCookieConsents($user->id, null);
|
|
|
|
// 필수(necessary)는 status 미저장 → 맵에서 제외. 선택형만 등장.
|
|
$this->assertEqualsCanonicalizing(
|
|
['analytics' => true, 'marketing' => false],
|
|
$consents
|
|
);
|
|
$this->assertCount(2, $consents);
|
|
$this->assertArrayNotHasKey('necessary', $consents);
|
|
}
|
|
|
|
/**
|
|
* 게스트는 history append-only 이므로 가장 최근 action 으로 카테고리별 동의 결정.
|
|
*/
|
|
public function test_get_current_cookie_consents_uses_latest_history_for_guest(): void
|
|
{
|
|
$sessionId = 'guest-session-zzz';
|
|
|
|
$this->service->updateConsent(null, $sessionId, 'cookie_analytics', true, 'banner');
|
|
$this->service->updateConsent(null, $sessionId, 'cookie_marketing', true, 'banner');
|
|
$this->service->updateConsent(null, $sessionId, 'cookie_marketing', false, 'preference_center');
|
|
|
|
$consents = $this->service->getCurrentCookieConsents(null, $sessionId);
|
|
|
|
$this->assertEqualsCanonicalizing(
|
|
['analytics' => true, 'marketing' => false],
|
|
$consents
|
|
);
|
|
$this->assertCount(2, $consents);
|
|
}
|
|
|
|
/**
|
|
* 정책 버전이 다른 동의는 결과에서 제외 (재동의 트리거).
|
|
*/
|
|
public function test_get_current_cookie_consents_excludes_outdated_policy_version(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
|
|
// 과거 정책 버전 동의 직접 삽입
|
|
GdprUserConsent::create([
|
|
'user_id' => $user->id,
|
|
'consent_key' => 'cookie_analytics',
|
|
'is_consented' => true,
|
|
'consented_at' => now(),
|
|
'consent_count' => 1,
|
|
'policy_version' => '0.9',
|
|
'last_source' => 'banner',
|
|
]);
|
|
|
|
$consents = $this->service->getCurrentCookieConsents($user->id, null);
|
|
|
|
$this->assertSame([], $consents);
|
|
}
|
|
|
|
/**
|
|
* 식별자 미제공 또는 동의 없음 → 빈 배열 반환.
|
|
*/
|
|
public function test_get_current_cookie_consents_returns_empty_when_no_identifier(): void
|
|
{
|
|
$this->assertSame([], $this->service->getCurrentCookieConsents(null, null));
|
|
$this->assertSame([], $this->service->getCurrentCookieConsents(null, ''));
|
|
}
|
|
|
|
/**
|
|
* 정책 버전 bump 후 같은 값으로 재호출 시 status.policy_version 갱신 + history INSERT.
|
|
*
|
|
* 회귀: noop 가드가 is_consented 만 비교하면 정책 v2 bump 후 "모두 동의" 클릭이
|
|
* 즉시 return 되어 status.policy_version 이 v1 에 머무르고 history 도 미생성.
|
|
* 결과: hasCurrentCookieConsent(v2) → false → needs_renewal 영구 true (Art.7(1) 입증 책임 위반).
|
|
* 가드는 (is_consented 동일) AND (policy_version 동일) 일 때만 noop 이어야 함.
|
|
*/
|
|
public function test_update_consent_renews_policy_version_when_value_unchanged_after_policy_bump(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
|
|
$this->service->updateConsent($user->id, null, 'cookie_analytics', true, 'banner');
|
|
|
|
// v1 동의 상태 검증
|
|
$beforeStatus = GdprUserConsent::where('user_id', $user->id)->first();
|
|
$this->assertSame('1', (string) $beforeStatus->policy_version);
|
|
$this->assertSame(1, $beforeStatus->consent_count);
|
|
$this->assertSame(1, GdprUserConsentHistory::where('user_id', $user->id)->count());
|
|
|
|
// 정책 버전 v2 bump
|
|
\Plugins\Sirsoft\Gdpr\Models\GdprPolicyVersion::create([
|
|
'version' => 2,
|
|
'change_type' => \Plugins\Sirsoft\Gdpr\Enums\GdprPolicyChangeType::Material->value,
|
|
'snapshot' => [],
|
|
]);
|
|
|
|
// bump 후 같은 값(true) 으로 재요청 — "모두 동의" 클릭 시나리오
|
|
$this->service->updateConsent($user->id, null, 'cookie_analytics', true, 'banner');
|
|
|
|
$afterStatus = GdprUserConsent::where('user_id', $user->id)->first();
|
|
$this->assertSame('2', (string) $afterStatus->policy_version, 'status.policy_version 이 v2 로 갱신되어야 함');
|
|
$this->assertSame(2, $afterStatus->consent_count, '재동의는 consent_count 1 증가 (Art.7 입증 책임)');
|
|
|
|
$this->assertSame(2, GdprUserConsentHistory::where('user_id', $user->id)->count(), 'v2 재동의 history 행 INSERT 되어야 함');
|
|
$this->assertDatabaseHas('gdpr_user_consent_histories', [
|
|
'user_id' => $user->id,
|
|
'consent_key' => 'cookie_analytics',
|
|
'action' => 'granted',
|
|
'policy_version' => '2',
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 같은 정책 버전 안에서 같은 값 재요청은 여전히 noop (중복 이력 방지 의도 유지).
|
|
*/
|
|
public function test_update_consent_noop_still_holds_within_same_policy_version(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
|
|
$this->service->updateConsent($user->id, null, 'cookie_analytics', true, 'banner');
|
|
$this->service->updateConsent($user->id, null, 'cookie_analytics', true, 'banner');
|
|
$this->service->updateConsent($user->id, null, 'cookie_analytics', true, 'banner');
|
|
|
|
$this->assertSame(1, GdprUserConsent::where('user_id', $user->id)->count());
|
|
$this->assertSame(1, GdprUserConsentHistory::where('user_id', $user->id)->count());
|
|
$this->assertSame(1, GdprUserConsent::where('user_id', $user->id)->first()->consent_count);
|
|
}
|
|
|
|
// ===== 이슈 #430 — 쿠키 거부 저장 =====
|
|
|
|
/**
|
|
* 선택형 카테고리 거부 시 is_rejected=true, rejected_at 기록, history action=rejected.
|
|
*
|
|
* STEP3-3 중복방지 가드 결함 회귀 가드: 가드가 is_consented+policy_version 만 비교하면
|
|
* 거부(false)와 일반 미동의(false)를 동일 취급 → is_rejected=true 저장이 누락된다.
|
|
*
|
|
* @scenario entry=reject, subject=member, category=optional
|
|
* @effects rejection_marks_optional_consent_as_rejected
|
|
*/
|
|
public function test_rejection_marks_optional_consent_as_rejected(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
|
|
$this->service->updateConsent(
|
|
$user->id, null, 'cookie_analytics', false, 'banner', null, true
|
|
);
|
|
|
|
$consent = GdprUserConsent::where('user_id', $user->id)->first();
|
|
$this->assertNotNull($consent);
|
|
$this->assertFalse($consent->is_consented);
|
|
$this->assertTrue((bool) $consent->is_rejected, '선택형 거부는 is_rejected=true 여야 함');
|
|
$this->assertNotNull($consent->rejected_at, 'rejected_at 이 기록되어야 함');
|
|
|
|
$this->assertDatabaseHas('gdpr_user_consent_histories', [
|
|
'user_id' => $user->id,
|
|
'consent_key' => 'cookie_analytics',
|
|
'action' => 'rejected',
|
|
'source' => 'banner',
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 거부 가드 결함 직접 재현: 이미 status row 가 미동의(false)로 존재해도
|
|
* 거부 신호가 오면 is_rejected 플래그가 저장되어야 한다.
|
|
*
|
|
* @scenario entry=reject, subject=member, category=optional
|
|
* @effects rejection_persists_even_when_status_already_false
|
|
*/
|
|
public function test_rejection_persists_even_when_status_already_false(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
|
|
// 1) 먼저 철회로 미동의(false) 상태를 만든다 (is_rejected=false).
|
|
$this->service->updateConsent($user->id, null, 'cookie_analytics', true, 'banner');
|
|
$this->service->updateConsent($user->id, null, 'cookie_analytics', false, 'mypage');
|
|
|
|
$before = GdprUserConsent::where('user_id', $user->id)->first();
|
|
$this->assertFalse((bool) $before->is_rejected, '사전조건: 아직 거부 아님');
|
|
|
|
// 2) 이제 명시적 거부 신호. is_consented 는 여전히 false 지만 is_rejected 는 true 로 바뀌어야 함.
|
|
$this->service->updateConsent(
|
|
$user->id, null, 'cookie_analytics', false, 'banner', null, true
|
|
);
|
|
|
|
$after = GdprUserConsent::where('user_id', $user->id)->first()->fresh();
|
|
$this->assertTrue((bool) $after->is_rejected, '가드 결함: is_rejected 저장 누락 (가드에 is_rejected 비교 필요)');
|
|
$this->assertNotNull($after->rejected_at);
|
|
}
|
|
|
|
/**
|
|
* 필수 카테고리는 동의 축에서 제외 — status·history 모두 저장하지 않는다 (이슈 #430 후속).
|
|
*
|
|
* 필수 쿠키는 ePrivacy Art.5(3) 면제로 동의 대상이 아니다. 거부·동의 어떤 신호가 와도
|
|
* "현재 상태"(status)나 "동의 이력"(history)에 기록하지 않는다. 필수를 저장하면
|
|
* "동의하지 않았는데 동의로 표시"되는 모순이 생기기 때문이다.
|
|
*
|
|
* @scenario entry=reject, subject=member, category=required
|
|
* @effects required_category_excluded_from_status
|
|
*/
|
|
public function test_required_category_is_excluded_from_status_and_history(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
|
|
// 필수에 대해 거부 신호를 포함해 호출해도 필수는 저장되지 않아야 함.
|
|
$this->service->updateConsent(
|
|
$user->id, null, 'cookie_necessary', true, 'banner', null, true
|
|
);
|
|
|
|
// status 미저장
|
|
$this->assertNull(
|
|
GdprUserConsent::where('user_id', $user->id)->where('consent_key', 'cookie_necessary')->first(),
|
|
'필수는 status 에 저장되지 않아야 함'
|
|
);
|
|
|
|
// history 미기록
|
|
$this->assertDatabaseMissing('gdpr_user_consent_histories', [
|
|
'user_id' => $user->id,
|
|
'consent_key' => 'cookie_necessary',
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 거부 후 재동의 시 is_rejected 플래그 자동 해제 + rejected_at null.
|
|
* history 는 append-only 라 rejected/granted 둘 다 보존.
|
|
*
|
|
* @scenario entry=reject_then_reaccept, subject=member, category=optional
|
|
* @effects re_consent_clears_rejection_flag_but_keeps_history
|
|
*/
|
|
public function test_re_consent_clears_rejection_flag_but_keeps_history(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
|
|
// 1) 거부
|
|
$this->service->updateConsent(
|
|
$user->id, null, 'cookie_analytics', false, 'banner', null, true
|
|
);
|
|
// 2) 재동의
|
|
$this->service->updateConsent($user->id, null, 'cookie_analytics', true, 'banner');
|
|
|
|
$consent = GdprUserConsent::where('user_id', $user->id)->first();
|
|
$this->assertTrue($consent->is_consented);
|
|
$this->assertFalse((bool) $consent->is_rejected, '재동의 시 거부 플래그 자동 해제');
|
|
$this->assertNull($consent->rejected_at, '재동의 시 rejected_at null 로 해제');
|
|
|
|
// history 는 rejected + granted 둘 다 보존
|
|
$this->assertDatabaseHas('gdpr_user_consent_histories', [
|
|
'user_id' => $user->id,
|
|
'consent_key' => 'cookie_analytics',
|
|
'action' => 'rejected',
|
|
]);
|
|
$this->assertDatabaseHas('gdpr_user_consent_histories', [
|
|
'user_id' => $user->id,
|
|
'consent_key' => 'cookie_analytics',
|
|
'action' => 'granted',
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 게스트 거부는 status 미사용, history 에만 action=rejected 로 기록.
|
|
*
|
|
* @scenario entry=reject, subject=guest, category=optional
|
|
* @effects guest_rejection_records_history_only
|
|
*/
|
|
public function test_guest_rejection_records_history_only(): void
|
|
{
|
|
$sessionId = 'guest-reject-xyz';
|
|
|
|
$this->service->updateConsent(
|
|
null, $sessionId, 'cookie_marketing', false, 'banner', null, true
|
|
);
|
|
|
|
$this->assertDatabaseCount('gdpr_user_consents', 0);
|
|
$this->assertDatabaseHas('gdpr_user_consent_histories', [
|
|
'session_id' => $sessionId,
|
|
'consent_key' => 'cookie_marketing',
|
|
'action' => 'rejected',
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* intent 미전달(기본값 false) 시 기존 동작 회귀 무영향 — 미동의는 revoked 로 기록.
|
|
*
|
|
* @scenario entry=save_selection, subject=member, category=optional
|
|
* @effects without_rejection_intent_false_is_revoked_not_rejected
|
|
*/
|
|
public function test_without_rejection_intent_false_is_revoked_not_rejected(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
|
|
$this->service->updateConsent($user->id, null, 'cookie_analytics', false, 'mypage');
|
|
|
|
$consent = GdprUserConsent::where('user_id', $user->id)->first();
|
|
$this->assertFalse((bool) $consent->is_rejected, '거부 신호 없으면 is_rejected=false');
|
|
|
|
$this->assertDatabaseHas('gdpr_user_consent_histories', [
|
|
'user_id' => $user->id,
|
|
'action' => 'revoked',
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* updateConsents 일괄 경로에도 거부 신호가 전달되어 선택형만 rejected 로 저장.
|
|
* (배너 "동의하지 않고 계속하기" 실제 호출 경로)
|
|
*
|
|
* 이슈 #430 후속 — 필수는 동의 축에서 제외되어 status 에 저장되지 않는다.
|
|
*
|
|
* @scenario entry=reject, subject=member, category=optional
|
|
* @effects update_consents_batch_applies_rejection_to_optional_only, required_category_excluded_from_status
|
|
*/
|
|
public function test_update_consents_batch_applies_rejection_to_optional_only(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
|
|
// 배너 거부: 필수=true, 선택형=false + isRejection=true
|
|
$this->service->updateConsents(
|
|
$user->id,
|
|
null,
|
|
[
|
|
'cookie_necessary' => true,
|
|
'cookie_analytics' => false,
|
|
'cookie_marketing' => false,
|
|
],
|
|
'banner',
|
|
true
|
|
);
|
|
|
|
$necessary = GdprUserConsent::where('user_id', $user->id)->where('consent_key', 'cookie_necessary')->first();
|
|
$analytics = GdprUserConsent::where('user_id', $user->id)->where('consent_key', 'cookie_analytics')->first();
|
|
$marketing = GdprUserConsent::where('user_id', $user->id)->where('consent_key', 'cookie_marketing')->first();
|
|
|
|
// 필수는 동의 축에서 제외 → status 행 자체가 생성되지 않음
|
|
$this->assertNull($necessary, '필수는 status 에 저장되지 않아야 함 (동의 축 제외)');
|
|
|
|
// 선택형만 거부(is_rejected=true) 로 저장
|
|
$this->assertFalse($analytics->is_consented);
|
|
$this->assertTrue((bool) $analytics->is_rejected, '선택형은 거부');
|
|
$this->assertFalse($marketing->is_consented);
|
|
$this->assertTrue((bool) $marketing->is_rejected, '선택형은 거부');
|
|
}
|
|
}
|