# API リファレンス: SPersona

`SPersona` は、`System.runAs` で使う**制限ユーザー (ペルソナ) を生成する**ためのビルダーです。`SBlueprint` と同じくイミュータブルで、各メソッドが新しいインスタンスを返します。

権限まわりのテストを書こうとすると、テストユーザーの作成に細かい落とし穴が並びます (一意な Username、mixed DML、ロケール既定値、Profile 名の解決)。`SPersona` はその「正しい 1 つの実装」を 1 クラスに集約しています。**`UserFactory` を自前で書かないでください。**

## Static ファクトリ

| メソッド | 用途 |
|---|---|
| `SPersona.of(String name)` | 起点。短いペルソナ名 (`'sales-rep'` など) を渡す。LastName などに使われる |

## 組み立て

| メソッド | 用途 |
|---|---|
| `profile(String profileName)` | Profile を**名前**で指定 (`'標準ユーザー'` / `'Standard User'` など)。`create()` 時に解決され、テスト実行全体でキャッシュされる |
| `permissionSets(String permissionSetName)` | 割り当てる PermissionSet を **API 名**で 1 つ追加 (ラベルではない) |
| `permissionSets(List<String> permissionSetNames)` | 同上、複数まとめて |
| `set(String fieldName, Object value)` | 任意の User 項目を上書き。`SBlueprint.set()` と同じく last wins |

## 生成

| メソッド | 戻り値 | 用途 |
|---|---|---|
| `create()` | `User` | Profile を解決し、安全な既定値で User を組み立て、PermissionSetAssignment ごと insert して返す |

```apex
User rep = SPersona.of('sales-rep')
  .profile('標準ユーザー')
  .permissionSets('InvoiceReadOnly')
  .set('LanguageLocaleKey', 'ja')
  .create();
```

## 知っておくべき挙動

- **Username は UUID で払い出される**ので、テストの並列実行でも衝突しません
- **ロケール系の既定値は実行ユーザー基準**で埋まります (org に依存しない)。変えたければ `set()` で上書き
- **mixed DML を内部で回避**しています (`System.runAs` でラップして insert)。通常の DML と混ぜて呼んで構いません
- **`create()` はテストコンテキスト専用**です。`System.runAs` に依存するため、本番コードからは呼べません
- **キャッシュはしません**。呼ぶたびに新しいユーザーが作られます (Profile 名 → Id の解決だけがキャッシュされます)
- ⚠️ **Profile 名はロケール依存**です。日本語 org では `'標準ユーザー'`、英語 org では `'Standard User'` になります

## 敵対的データとセットで使う

`SPersona` が本領を発揮するのは、`SBlueprint` の `owner()` / `sharedWith()` と組み合わせたときです。「管理者が所有し、テスト対象のペルソナには最小限だけ共有する」という**敵対的なデータ**を宣言できます。

```apex
User admin = SPersona.of('admin').profile('システム管理者').create();
User rep   = SPersona.of('sales-rep').profile('標準ユーザー').create();

SOrchestrator orchestrator = SOrchestrator.start()
  .add(
    SBlueprint.of(Invoice__c.class).alias('inv')
      .owner(admin)                //  admin 所有
      .sharedWith(rep, 'Read'));   //  rep には Read だけ
orchestrator.create();

System.runAs(rep) {
  // rep から何が見えるか / 何が書けるかを検証する
}
```

**`sharedWith` の有無が、そのまま可視性の期待値になります**。共有を宣言していないレコードが `runAs` の中で見えてしまったら、それは共有設定の穴です。

## プロジェクト側での集約

`Blueprints.cls` と同じ考え方で、**プロジェクト固有のペルソナは単一の `Personas.cls` に集約**します。テストごとに Profile 名や PermissionSet 名を直書きすると、org 側の改名で一斉に壊れたときの修正箇所が散らばります。

```apex
public with sharing class Personas {
  /** 一般の営業担当 */
  public static User salesRep() {
    return SPersona.of('sales-rep')
      .profile('標準ユーザー')
      .permissionSets('InvoiceReadOnly')
      .create();
  }
}
```

## 関連ドキュメント

- [API リファレンス: SBlueprint](/ja/apex-stem/docs/apex-blueprint-api-sblueprint): `owner` / `sharedWith` で敵対的データを宣言する
- [API リファレンス: SOrchestrator](/ja/apex-stem/docs/apex-blueprint-api-sorchestrator): 依存解決と実 DML
- [ApexBlueprint ガイド](/ja/apex-stem/docs/apex-blueprint-guide): ガイド目次に戻る
