---
generated_at: 2025-12-29 17:45:00
metrics:
  claims_total: 123
  claims_with_evidence: 123
  claims_without_evidence: 0
confidence_derived: 1.00
---

# 単体テストケース一覧 根拠レポート - security モジュール

## 概要

このレポートは、securityモジュールの単体テストケース一覧(security.csv)の各テストケースに対する根拠を示すものです。

## 解析対象ファイル

| ファイルパス | 種別 |
|-------------|------|
| `plugins/webkul/security/src/Models/User.php` | Eloquentモデル |
| `plugins/webkul/security/src/Models/Role.php` | Eloquentモデル |
| `plugins/webkul/security/src/Models/Team.php` | Eloquentモデル |
| `plugins/webkul/security/src/Models/Permission.php` | Eloquentモデル |
| `plugins/webkul/security/src/Models/Invitation.php` | Eloquentモデル |
| `plugins/webkul/security/src/Models/Scopes/UserPermissionScope.php` | Eloquentスコープ |
| `plugins/webkul/security/src/Traits/HasScopedPermissions.php` | トレイト |
| `plugins/webkul/security/src/Enums/CompanyStatus.php` | Enum |
| `plugins/webkul/security/src/Enums/PermissionType.php` | Enum |
| `plugins/webkul/security/src/Policies/RolePolicy.php` | ポリシー |
| `plugins/webkul/security/src/Policies/UserPolicy.php` | ポリシー |
| `plugins/webkul/security/src/Policies/TeamPolicy.php` | ポリシー |
| `plugins/webkul/security/src/PermissionRegistrar.php` | サービスクラス |
| `plugins/webkul/security/src/Mail/UserInvitationMail.php` | Mailable |
| `plugins/webkul/security/src/Livewire/AcceptInvitation.php` | Livewireコンポーネント |
| `plugins/webkul/security/src/Settings/CurrencySettings.php` | 設定クラス |
| `plugins/webkul/security/src/Settings/UserSettings.php` | 設定クラス |

## テストケース根拠一覧

### Userモデル (UT-SEC-001 ~ UT-SEC-016)

#### UT-SEC-001: canAccessPanel

**根拠コード** (`plugins/webkul/security/src/Models/User.php` 42-45行目):
```php
public function canAccessPanel(Panel $panel): bool
{
    return true;
}
```
常にtrueを返すため、正常系テストケースを設定。

#### UT-SEC-002, UT-SEC-003: getAvatarUrlAttribute

**根拠コード** (`plugins/webkul/security/src/Models/User.php` 47-50行目):
```php
public function getAvatarUrlAttribute()
{
    return $this->partner?->avatar_url;
}
```
null安全演算子(`?->`)を使用しているため、パートナーが存在しない場合はnullを返す。

#### UT-SEC-004 ~ UT-SEC-010: リレーションメソッド

**根拠コード** (`plugins/webkul/security/src/Models/User.php` 52-85行目):
```php
public function teams(): BelongsToMany
{
    return $this->belongsToMany(Team::class, 'user_team', 'user_id', 'team_id');
}

public function employee()
{
    return $this->hasOne(Employee::class, 'user_id');
}

public function departments()
{
    return $this->hasMany(Department::class, 'manager_id');
}

public function companies(): HasMany
{
    return $this->hasMany(Company::class);
}

public function partner()
{
    return $this->belongsTo(Partner::class, 'partner_id');
}

public function allowedCompanies(): BelongsToMany
{
    return $this->belongsToMany(Company::class, 'user_allowed_companies', 'user_id', 'company_id');
}

public function defaultCompany(): BelongsTo
{
    return $this->belongsTo(Company::class, 'default_company_id');
}
```

#### UT-SEC-011 ~ UT-SEC-014: bootメソッドとパートナー処理

**根拠コード** (`plugins/webkul/security/src/Models/User.php` 87-129行目):
```php
protected static function boot()
{
    parent::boot();

    static::saved(function ($user) {
        if (! $user->partner_id) {
            $user->handlePartnerCreation($user);
        } else {
            $user->handlePartnerUpdation($user);
        }
    });
}

private function handlePartnerCreation(self $user)
{
    $partner = $user->partner()->create([
        'creator_id' => Auth::user()->id ?? $user->id,
        'user_id'    => $user->id,
        'sub_type'   => 'partner',
        ...Arr::except($user->toArray(), ['id']),
    ]);

    $user->partner_id = $partner->id;
    $user->save();
}

private function handlePartnerUpdation(self $user)
{
    $partner = Partner::updateOrCreate(
        ['id' => $user->partner_id],
        [
            'creator_id' => Auth::user()->id ?? $user->id,
            'user_id'    => $user->id,
            'sub_type'   => 'partner',
            ...Arr::except($user->toArray(), ['id']),
        ]
    );

    if ($user->partner_id !== $partner->id) {
        $user->partner_id = $partner->id;
        $user->save();
    }
}
```

#### UT-SEC-015: コンストラクタ

**根拠コード** (`plugins/webkul/security/src/Models/User.php` 24-36行目):
```php
public function __construct(array $attributes = [])
{
    parent::__construct($attributes);

    $this->mergeFillable([
        'partner_id',
        'language',
        'is_active',
        'default_company_id',
        'resource_permission',
        'is_default',
    ]);
}
```

#### UT-SEC-016: casts

**根拠コード** (`plugins/webkul/security/src/Models/User.php` 38-40行目):
```php
protected $casts = [
    'default_company_id' => 'integer',
];
```

### Roleモデル (UT-SEC-017 ~ UT-SEC-018)

**根拠コード** (`plugins/webkul/security/src/Models/Role.php` 10-13行目):
```php
public function getNameAttribute($value)
{
    return Str::ucfirst($value);
}
```
`Str::ucfirst`は先頭文字を大文字化する。空文字列の場合は空文字列を返す。

### Teamモデル (UT-SEC-019 ~ UT-SEC-020)

**根拠コード** (`plugins/webkul/security/src/Models/Team.php` 15-25行目):
```php
protected $fillable = [
    'name',
];

public function users(): BelongsToMany
{
    return $this->belongsToMany(User::class, 'user_team', 'team_id', 'user_id');
}
```

### Permissionモデル (UT-SEC-021 ~ UT-SEC-023)

**根拠コード** (`plugins/webkul/security/src/Models/Permission.php` 14-19行目):
```php
protected static function getPermissions(array $params = [], bool $onlyOne = false): Collection
{
    return app(PermissionRegistrar::class)
        ->setPermissionClass(static::class)
        ->getPermissions($params, $onlyOne);
}
```

### Invitationモデル (UT-SEC-024 ~ UT-SEC-025)

**根拠コード** (`plugins/webkul/security/src/Models/Invitation.php` 10-14行目):
```php
protected $table = 'user_invitations';

protected $guarded = [];
```

### UserPermissionScope (UT-SEC-026 ~ UT-SEC-029)

**根拠コード** (`plugins/webkul/security/src/Models/Scopes/UserPermissionScope.php` 17-51行目):
```php
public function __construct(string $ownerRelation)
{
    $this->ownerRelation = $ownerRelation;
}

public function apply(Builder $builder, Model $model): void
{
    $user = Auth::user();

    if ($user->resource_permission === PermissionType::GLOBAL->value) {
        return;
    }

    if ($user->resource_permission === PermissionType::INDIVIDUAL->value) {
        $builder->whereHas($this->ownerRelation, function ($q) use ($user) {
            $q->where('users.id', $user->id);
        });

        $builder->orWhereHas('followers', function ($q) use ($user) {
            $q->where('chatter_followers.partner_id', $user->partner_id);
        });
    }

    if ($user->resource_permission === PermissionType::GROUP->value) {
        $teamIds = $user->teams()->pluck('id');

        $builder->whereHas("$this->ownerRelation.teams", function ($q) use ($teamIds) {
            $q->whereIn('teams.id', $teamIds);
        });
    }
}
```

### HasScopedPermissionsトレイト (UT-SEC-030 ~ UT-SEC-042)

**根拠コード** (`plugins/webkul/security/src/Traits/HasScopedPermissions.php` 14-82行目):
```php
protected function hasGlobalAccess(User $user): bool
{
    return $user->resource_permission === PermissionType::GLOBAL->value;
}

protected function hasGroupAccess(User $user, Model $model, string $ownerAttribute = 'user'): bool
{
    if ($user->resource_permission !== PermissionType::GROUP->value) {
        return false;
    }

    $owner = $model->{$ownerAttribute};

    if (! $owner) {
        return false;
    }

    if ($owner instanceof Collection) {
        if ($owner->pluck('id')->contains($user->id)) {
            return true;
        }

        $ownerTeamIds = $owner->pluck('teams')->flatten()->pluck('id');
    } else {
        if ($owner->id === $user->id) {
            return true;
        }

        $ownerTeamIds = $owner->teams->pluck('id');
    }

    $userTeamIds = $user->teams->pluck('id');

    return $ownerTeamIds->intersect($userTeamIds)->isNotEmpty();
}

protected function hasIndividualAccess(User $user, Model $model, $ownerAttribute = 'user'): bool
{
    if ($user->resource_permission !== PermissionType::INDIVIDUAL->value) {
        return false;
    }

    $owner = $model->{$ownerAttribute};

    if (! $owner) {
        return false;
    }

    return $owner instanceof Collection
        ? $owner->pluck('id')->contains($user->id)
        : $owner->id === $user->id;
}

protected function hasAccess(User $user, Model $model, string $ownerAttribute = 'user'): bool
{
    return $this->hasGlobalAccess($user)
        || $this->hasGroupAccess($user, $model, $ownerAttribute)
        || $this->hasIndividualAccess($user, $model, $ownerAttribute);
}
```

### Enum (UT-SEC-043 ~ UT-SEC-049)

#### CompanyStatus

**根拠コード** (`plugins/webkul/security/src/Enums/CompanyStatus.php` 5-18行目):
```php
enum CompanyStatus: string
{
    case ACTIVE = 'active';
    case INACTIVE = 'inactive';

    public static function options(): array
    {
        return [
            self::ACTIVE->value      => __('security::enums/company-status.active'),
            self::INACTIVE->value    => __('security::enums/company-status.inactive'),
        ];
    }
}
```

#### PermissionType

**根拠コード** (`plugins/webkul/security/src/Enums/PermissionType.php` 5-21行目):
```php
enum PermissionType: string
{
    case GROUP = 'group';
    case INDIVIDUAL = 'individual';
    case GLOBAL = 'global';

    public static function options(): array
    {
        return [
            self::GROUP->value      => __('security::enums/permission-type.group'),
            self::INDIVIDUAL->value => __('security::enums/permission-type.individual'),
            self::GLOBAL->value     => __('security::enums/permission-type.global'),
        ];
    }
}
```

### ポリシー (UT-SEC-050 ~ UT-SEC-076)

#### RolePolicy

**根拠コード** (`plugins/webkul/security/src/Policies/RolePolicy.php` 16-99行目):
```php
public function viewAny(User $user): bool
{
    return $user->can('view_any_role');
}

public function view(User $user, Role $role): bool
{
    return $user->can('view_role');
}

public function create(User $user): bool
{
    return $user->can('create_role');
}

public function update(User $user, Role $role): bool
{
    return $user->can('update_role');
}

public function delete(User $user, Role $role): bool
{
    return $user->can('delete_role');
}

public function deleteAny(User $user): bool
{
    return $user->can('delete_any_role');
}

public function forceDelete(User $user, Role $role): bool
{
    return $user->can('force_delete_field');
}

public function forceDeleteAny(User $user): bool
{
    return $user->can('force_delete_any_field');
}

public function restore(User $user, Role $role): bool
{
    return $user->can('restore_field');
}

public function restoreAny(User $user): bool
{
    return $user->can('restore_any_field');
}

public function reorder(User $user): bool
{
    return $user->can('reorder_field');
}
```

#### UserPolicy

**根拠コード** (`plugins/webkul/security/src/Policies/UserPolicy.php` 15-90行目):
各メソッドは対応する権限(`view_any_user`, `view_user`, `create_user`, `update_user`, `delete_user`, `delete_any_user`, `force_delete_user`, `force_delete_any_user`, `restore_user`, `restore_any_user`)をチェックしている。

#### TeamPolicy

**根拠コード** (`plugins/webkul/security/src/Policies/TeamPolicy.php` 16-51行目):
各メソッドは対応する権限(`view_any_team`, `view_team`, `create_team`, `update_team`, `delete_team`)をチェックしている。

### PermissionRegistrar (UT-SEC-077 ~ UT-SEC-102)

**根拠コード** (`plugins/webkul/security/src/PermissionRegistrar.php`):

主要メソッドの根拠:

- `__construct` (56-64行目): CacheManagerを受け取り、各クラスとリゾルバを初期化
- `initializeCache` (66-79行目): キャッシュ設定を構成ファイルから読み込み
- `getCacheStoreFromConfig` (81-98行目): デフォルト/カスタム/未定義ドライバの処理
- `registerPermissions` (122-134行目): Gateにbeforeコールバックを登録
- `forgetCachedPermissions` (139-145行目): 権限キャッシュをクリア
- `isUid` (404-423行目): UUID/ULIDの正規表現判定

```php
public static function isUid($value): bool
{
    if (! is_string($value) || empty(trim($value))) {
        return false;
    }

    // check if is UUID/GUID
    $uid = preg_match('/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iD', $value) > 0;
    if ($uid) {
        return true;
    }

    // check if is ULID
    $ulid = strlen($value) == 26 && strspn($value, '0123456789ABCDEFGHJKMNPQRSTVWXYZabcdefghjkmnpqrstvwxyz') == 26 && $value[0] <= '7';
    if ($ulid) {
        return true;
    }

    return false;
}
```

### Mail (UT-SEC-103 ~ UT-SEC-106)

**根拠コード** (`plugins/webkul/security/src/Mail/UserInvitationMail.php` 21-64行目):
```php
public function __construct(Invitation $invitation)
{
    $this->invitation = $invitation;
}

public function envelope(): Envelope
{
    return new Envelope(
        subject: __('security::mail/user-invitation-mail.user-invitation.subject', [
            'app' => config('app.name'),
        ]),
    );
}

public function content(): Content
{
    return new Content(
        markdown: 'security::emails.user-invitation',
        with: [
            'acceptUrl' => URL::signedRoute(
                'security.invitation.accept',
                ['invitation' => $this->invitation]
            ),
        ]
    );
}

public function attachments(): array
{
    return [];
}
```

### Livewire AcceptInvitation (UT-SEC-107 ~ UT-SEC-116)

**根拠コード** (`plugins/webkul/security/src/Livewire/AcceptInvitation.php`):

```php
public function mount(): void
{
    $this->invitationModel = Invitation::findOrFail($this->invitation);

    $this->form->fill([
        'email' => $this->invitationModel->email,
    ]);
}

public function create(): void
{
    $this->invitationModel = Invitation::find($this->invitation);

    $user = User::create([
        'name'               => $this->form->getState()['name'],
        'password'           => $this->form->getState()['password'],
        'email'              => $this->invitationModel->email,
        'default_company_id' => app(UserSettings::class)->default_company_id,
    ]);

    $user->assignRole(app(UserSettings::class)->default_role_id);

    $this->invitationModel->delete();

    $this->redirect(Dashboard::getUrl());
}

public function getHeading(): string
{
    return 'Accept Invitation';
}

public function hasLogo(): bool
{
    return false;
}
```

### Settings (UT-SEC-117 ~ UT-SEC-123)

#### CurrencySettings

**根拠コード** (`plugins/webkul/security/src/Settings/CurrencySettings.php` 7-15行目):
```php
class CurrencySettings extends Settings
{
    public ?int $default_currency_id;

    public static function group(): string
    {
        return 'currency';
    }
}
```

#### UserSettings

**根拠コード** (`plugins/webkul/security/src/Settings/UserSettings.php` 7-21行目):
```php
class UserSettings extends Settings
{
    public bool $enable_user_invitation;

    public bool $enable_reset_password;

    public ?int $default_role_id;

    public ?int $default_company_id;

    public static function group(): string
    {
        return 'general';
    }
}
```

## 優先度分布

| 優先度 | 件数 | 割合 |
|-------|------|------|
| 高 | 55 | 44.7% |
| 中 | 47 | 38.2% |
| 低 | 21 | 17.1% |

## テスト観点分布

| 観点 | 件数 | 割合 |
|------|------|------|
| 正常系 | 115 | 93.5% |
| 異常系 | 6 | 4.9% |
| 境界値 | 2 | 1.6% |

## 備考

- securityモジュールは認証・認可の中核機能を担うため、高優先度のテストケースが多い
- PermissionRegistrarはSpatie Permissionパッケージの拡張として動作するため、キャッシュ関連のテストが重要
- HasScopedPermissionsトレイトは他モジュールでも使用されるため、徹底的なテストが必要
- ポリシークラスは権限チェックの一貫性を保証するため、全メソッドをカバー
