# API Reference: SPersona

`SPersona` is a builder for **creating the restricted users ("personas") you run `System.runAs` against**. Like `SBlueprint`, it is immutable — every method returns a new instance.

Writing permission tests means creating a test user, and that comes with a pile of subtle traps (a unique Username, mixed DML, locale defaults, resolving the Profile name). `SPersona` centralises the one correct implementation in a single class. **Don't hand-write a `UserFactory`.**

## Static Factory

| Method | Purpose |
|---|---|
| `SPersona.of(String name)` | The entry point. Pass a short persona name (`'sales-rep'`), used for `LastName` and friends |

## Building

| Method | Purpose |
|---|---|
| `profile(String profileName)` | Set the Profile **by name** (`'Standard User'`, `'Minimum Access - Salesforce'`). Resolved at `create()` and cached for the whole test run |
| `permissionSets(String permissionSetName)` | Add one PermissionSet by **API name** (not the label) |
| `permissionSets(List<String> permissionSetNames)` | Same, several at once |
| `set(String fieldName, Object value)` | Override any User field. Same last-wins semantics as `SBlueprint.set()` |

## Creating

| Method | Returns | Purpose |
|---|---|---|
| `create()` | `User` | Resolves the profile, builds the User with safe defaults, inserts it together with its PermissionSetAssignments, and returns it |

```apex
User rep = SPersona.of('sales-rep')
  .profile('Standard User')
  .permissionSets('InvoiceReadOnly')
  .set('LanguageLocaleKey', 'en_US')
  .create();
```

## Behaviour worth knowing

- **Usernames are issued as UUIDs**, so they never collide under parallel test execution
- **Locale defaults come from the running user**, keeping them org-independent. Override with `set()` if you need to
- **Mixed DML is avoided internally** (the insert is wrapped in `System.runAs`), so you can mix it freely with ordinary DML
- **`create()` is test-context only.** It relies on `System.runAs`, so production code cannot call it
- **Nothing is cached per call.** Every call creates a fresh user (only the Profile-name-to-Id resolution is cached)
- ⚠️ **Profile names are locale-dependent.** `'Standard User'` in an English org, `'標準ユーザー'` in a Japanese one

## Use it with hostile data

`SPersona` earns its keep alongside `SBlueprint`'s `owner()` / `sharedWith()`. Together they declare **hostile data**: owned by an admin, with only the bare minimum shared to the persona under test.

```apex
User admin = SPersona.of('admin').profile('System Administrator').create();
User rep   = SPersona.of('sales-rep').profile('Standard User').create();

SOrchestrator orchestrator = SOrchestrator.start()
  .add(
    SBlueprint.of(Invoice__c.class).alias('inv')
      .owner(admin)                //  owned by admin
      .sharedWith(rep, 'Read'));   //  rep gets Read only
orchestrator.create();

System.runAs(rep) {
  // Verify what rep can see, and what rep can write
}
```

**Whether a record was `sharedWith` becomes the expected visibility.** If a record you never shared turns up inside the `runAs` block, that is a hole in your sharing configuration.

## Consolidating on the project side

Follow the same idea as `Blueprints.cls`: **keep project-specific personas in a single `Personas.cls`**. Spell Profile names and PermissionSet names inline across tests and a rename on the org side scatters the fix across every file.

```apex
public with sharing class Personas {
  /** A rank-and-file sales rep */
  public static User salesRep() {
    return SPersona.of('sales-rep')
      .profile('Standard User')
      .permissionSets('InvoiceReadOnly')
      .create();
  }
}
```

## Related Documents

- [API Reference: SBlueprint](/apex-stem/docs/apex-blueprint-api-sblueprint): declaring hostile data with `owner` / `sharedWith`
- [API Reference: SOrchestrator](/apex-stem/docs/apex-blueprint-api-sorchestrator): dependency resolution and real DML
- [ApexBlueprint guide](/apex-stem/docs/apex-blueprint-guide): back to the guide index
