# runas

> Runs a block as a given principal, so security rules apply exactly as they would for that user. It is how you test that a rule denies the people it should — the only way to prove security from inside the app. Naming an arbitrary principal is TEST-ONLY: outside test code (a `[Test]`, a `[TestFixture]`, or a helper in a `.test.osy` file) `runas` runs only as a declared actor, and any other operand is a compile error.

<!-- id: testing-runas · area: testing · stability: stable · html: https://osysharp.com/reference/testing/runas/ -->

## Summary        {#summary}
`runas (principal) { … }` runs the block **as that user**. Every security rule inside it evaluates against them: row
filters bind to them, role rules activate for their roles, and what they may not do is refused.

It exists so you can test the thing that is hardest to test and worst to get wrong — that your rules deny the people
they should.

**Naming a principal this way works only in test code** — a `[Test]`, a `[TestFixture]` body, or a helper declared in
a `.test.osy` file. Anywhere else `runas` runs as a declared actor and nothing else
([[Actor] — a principal that code may run as](https://osysharp.com/reference/security/declared-actor/)); every other operand is a compile error, and that is deliberate — see
[Why it is test-only](#test-only) below.

## Signature      {#signature}
```osy syntax
runas (<principalRow>) {
  … // everything in here acts as that user
}

runas (AuthBootstrap) {
  … // everything in here acts as the platform's ephemeral auth principal
}

runas (new Visitor { Id = "guest-b" }) {
  … // everything in here is ANOTHER BROWSER — the principal stays as it is
}
```

## Description    {#description}

### Seeing what a user sees   {#visibility}
A row filter like `allow read where Owner == user` means different rows exist for different people. `runas` is how you
observe that:

```osy title="the model: a row filter over the owner" test app=testing-runas
[Principal] entity User {
  [Required] string Name;
}

entity Doc {
  User Owner;
  [MaxLength(200)] string Title;
  security { allow read where Owner == user; }
}

```

The test that observes it, and one detail in it that is not decoration: **`principal` is how a test gets hold of the
person it wants to be.** A `[Test]` body outside a `runas` is an anonymous caller (see [Outside the block](#outside)),
so a `User.Single(…)` written there reads nothing and there is nobody to become. A `principal` declaration resolves
**unsecured**, like the fixture:

```osy title="each user sees only their own rows" run app=testing-runas
principal Alice => User.Single(u => u.Name == "Alice");
principal Bob   => User.Single(u => u.Name == "Bob");

[TestFixture]
void Seed() {
  var alice = new User { Name = "Alice" };
  var bob = new User { Name = "Bob" };
  var aliceDoc = new Doc { Owner = alice, Title = "alice-doc" };
  var bobDoc = new Doc { Owner = bob, Title = "bob-doc" };
}

[Test(Seed)]
void A_user_sees_only_their_own_docs() {
  runas(Alice) {
    Assert.Equal(1, Doc.Count());                          // Bob's row is not merely hidden — it does not exist for her
    Assert.Equal("alice-doc", Doc.Single(d => true).Title);
  }
}
```

Read that first assertion carefully. Under `runas(Alice)`, `Doc.Count()` is **1**. The filter is not a mask applied
after the fact; it is part of the query. Bob's row is not in the result set to be filtered out — it was never in it.

### Proving the denial   {#denial}
Pair `runas` with [`Assert.Denied`](https://osysharp.com/reference/testing/assert/) to claim that someone is refused:

```osy title="the rule denies the person it should" run app=testing-runas
[Test(Seed)]
void Bob_cannot_read_Alices_doc() {
  runas(Bob) {
    Assert.Equal(0, Doc.Count(d => d.Owner == Alice));   // Alice's doc is not his to see
  }
}
```

### Outside the block — the fixture is unrestricted, the test body is NOBODY   {#outside}
These are two different things, and reading them as one is a half-hour lost:

- A **`[TestFixture]`** runs **unsecured**, so it can seed rows across every entity without fighting the rules it is
  about to test.
- A **`[Test]` body** outside a `runas` block runs as **an anonymous caller** — secured, with no principal. Under
  deny-by-default a read there returns nothing, which is why a `Assert.NotNull` on it fails rather than passing on
  unrestricted access.

**Until something signs you in.** Driving the app's own sign-in — `Ui.SignInAs(Alice)`, or filling and submitting
its login form — makes the test body that person for the rest of the test, exactly as it makes the browser that
person. Reads after it are Alice's reads and need no `runas` wrapper; reads written *above* it are still nobody's.
`Ui.SignOut()` takes it away again.

That asymmetry is deliberate, and it is worth stating plainly: **security is only tested inside `runas`, or as
somebody you signed in.** A test that does neither has tested what nobody can do.

**A row carried out of the block is read again by whoever the test is now.** Assign a row to a variable inside
`runas (Alice) { … }` and read it after the block, and it is read as the test body — nobody — exactly as it would be
for them in the app. When nobody may read it (or that field of it), the read stops the test and says so: *"….Subject
reads as null here because this row was read inside `runas (Alice)` and is used after that block ended…"*. Make the
assertion inside the block, or copy the values you need into a variable or a class before it ends. Carrying a row
into *another* `runas` block is different and is answered as before: that principal is served only what they may read.

### Seeding as somebody — `runas` in a fixture   {#fixture}
A fixture writes **unsecured**, but some of what it seeds goes through the app's own functions, and those may ask who
is acting — an `[Authorize]` function, or one that reads `Session.CurrentUser`. Write the `runas` straight in the
fixture's body: the person it names is bound for the block (their roles too), exactly as in a test. What the fixture
writes still writes past `security { }`; `runas` only says who the app's code is acting for.

```osy title="a fixture that seeds as the person it names" run app=testing-runas
principal Carol => User.Single(u => u.Name == "Carol");

// An ordinary function that files a doc for whoever is acting — so it needs SOMEBODY to be acting.
void FileForWhoeverIsActing(string title) {
  new Doc { Owner = User.Single(u => u.Id == Session.CurrentUser!.Id), Title = title };
}

[TestFixture]
void CarolsDesk() {
  new User { Name = "Carol" };
  runas (Carol) { FileForWhoeverIsActing("carol-doc"); }
}

[Test(CarolsDesk)]
void The_fixture_filed_it_as_Carol() {
  runas (Carol) { Assert.Equal("carol-doc", Doc.Single(d => true).Title); }
}
```

### Does a helper need its own runas?   {#helper}
No — **the acting principal flows down the whole call chain.** `runas` rebinds who is asking for the rest of the
block, calls it makes included, so an ordinary function has nothing extra to do: it inherits whoever is running it.

```osy title="a plain helper acts as whoever called it" run app=testing-runas
// AN ORDINARY FUNCTION — not [Test], not [TestFixture], and no runas of its own (writing one here hits the compile
// error below in an app file). It needs none: whoever the CALLER's runas bound is still bound
// while this statement runs.
Doc FindMyDoc(string title) => Doc.FirstOrDefault(d => d.Title == title);

[Test(Seed)]
void A_helper_acts_as_whoever_called_it() {
  runas(Alice) {
    Assert.NotNull(FindMyDoc("alice-doc"));   // her own row filter is evaluated as HER, from inside the helper
  }
  runas(Bob) {
    Assert.Null(FindMyDoc("alice-doc"));      // the SAME helper, called as Bob, sees nothing — RLS still applies
  }
}
```

So a shared helper is written **once**, with no principal ceremony of its own, and **the test switches who is
acting between calls**:

```osy title="two people in one test — the CALLER switches, the helper never knows" syntax
runas(Mia)  { Found(); Invite(); }   // Mia founds the org and sends the invite
runas(Otto) { Accept(); }            // Otto — a DIFFERENT principal — accepts it
```

`Found`, `Invite` and `Accept` each run as whoever called them; none needs a `runas` of its own, and a helper
declared in a `.test.osy` file MAY declare one ("as staff, approve this"), since everything in a test file is test
code — but the same helper in an app file is refused (see [Why it is test-only](#test-only) below). A helper
that itself needs to act as TWO people within one call is the wrong shape: split it, and let the caller switch
between calls instead, exactly as above.

### Reading a credential, as the auth flow   {#auth-bootstrap}
`runas (AuthBootstrap)` is the one form whose argument is not a row. It becomes the **same user-less ephemeral
principal the platform arms an `[AuthMethod]` with**: no current user, bearing the `[Role]` your
`app.AuthBootstrap` declares, with exactly that role's ordinary `security {}` grants and nothing more.

It exists because a properly-masked credential has exactly one legitimate reader, and until this form a test could not
be it:

```osy title="the mask that leaves a credential one legitimate reader" syntax
security {
  deny read PasswordHash when !IsAuthenticator;   // the shape every app is told to write
  deny read ResetToken   when !IsAuthenticator;
}
```

That mask makes the field unreadable to **every principal a test can name** — which is correct, and which left a test
needing to observe what the auth flow observes with nowhere to stand. The practical result was worse than the gap: an
app whose reset flow had to be tested simply left the token unmasked, and an unmasked credential on the `[Principal]`
rides to the browser on the `Session.CurrentUser` payload.

```osy title="reading that token, as the auth flow itself" syntax
// what the email carried — read as the only thing allowed to see it
string token = "";
runas (AuthBootstrap) { token = User.Single(u => u.Email == "ada@example.com").ResetToken; }
```

⛔ **This peek stands in for the mailbox, and it is honest only if there IS one.** Over an app whose reset flow
really mails the token, reading the column as the auth principal is a fair substitute for opening the mail. Over an
app that mints a token and sends nothing, the identical two lines are a back door: the test completes a flow no user
could, and reports that the flow works. That is not a hypothetical — `demo/auth-demo` shipped exactly that, with
four green tests over it, until 2026-09-04. So pair this peek with something that asserts the token actually LEFT
(the provider's message id on the row, say); `security-reset-token-never-delivered` (MUST) catches the app-shaped
half.

**It grants no new authority.** It is the arming the platform already performs for `Login`, `Signup` and
`PasswordReset`, reachable from a test — so what it can read is what your own auth flow can read, decided entirely by
the grants you wrote. It stays test-only like every other `runas`, and it fails loudly rather than quietly running
anonymously if the app declares no `app.AuthBootstrap` — because a block that reads a masked field while bound to
nobody reads `null`, and an assertion over that would pass for the wrong reason.

### Being another browser   {#visitor}
A guest's rows are their own VISIT's ([guest rows (a visit's own rows)](https://osysharp.com/reference/security/guest-rows/)), and a test is one browser — a fresh visitor, whose
`Ui.*` screen is handed the same one. `runas (new Visitor { Id = "…" })` makes a block a different browser. Only the
visitor changes: signed in or not stays as it was, so nesting a principal inside it is "that person, on that browser",
and `Ui.*` verbs inside it drive a screen that is that visitor too. A `return` inside any `runas` block returns from the
function, so a helper can answer as a visitor:

```osy syntax
int BasketsOf(string visit) {
  runas (new Visitor { Id = visit }) { return Basket.Count(); }
}
```

## Why it is test-only   {#test-only}
`runas` rebinds the acting user **and that user's roles**. Inside the block, every rule you wrote evaluates for
somebody else. That is exactly what a security test needs and exactly what application code must never be able to do:
it would let any code become any user it could merely *look up*, and on the ordinary shape where signed-in users can
read the user directory, that is everybody.

The platform's promise is that you decide security **once**, on the entity, and it then holds everywhere without you
checking — you never have to ask whether a particular read, function or screen honours it. That promise only survives
while nothing in the language can step around it. So the compiler refuses `runas` to a principal outside test code,
and the runtime refuses it again in any run a test or fixture did not start:

```text
`runas(...)` is a TEST-ONLY construct and this is not test code (a [Test], a [TestFixture], or a helper in a
`.test.osy` file). It rebinds the acting principal and that principal's roles, which bypasses the security you
declared on your entities — so app code may not speak it.
```

**If a job needs an identity of its own** — a nightly run, a machine, an assistant — declare it: a class marked
`[Actor]` is the one thing code outside a test may run as, with the roles its declaration names and no others
([[Actor] — a principal that code may run as](https://osysharp.com/reference/security/declared-actor/)). A test may run as one too: `runas (BillingRobot) { … }` means the same in both.

**If you need authority before anyone is signed in** — a login, a signup, an OAuth callback, which must read or write
user rows with no user yet — that is [auth bootstrap (login, before anyone is signed in)](https://osysharp.com/reference/security/auth-bootstrap/), not this. You declare a role and a `policy` that
leashes it, mark the entry points [[security-auth-method|`[AuthMethod]`]], and the platform runs them as an ephemeral
principal bearing that role. The elevation is declared in one place a reviewer can find, bounded by a predicate, and
still not something app code can grant itself.

## See also       {#see-also}
- [Assert](https://osysharp.com/reference/testing/assert/) — `Assert.Denied`, the assertion `runas` makes possible
- [secure by default (deny-all)](https://osysharp.com/reference/security/secure-by-default/) — what is denied before you write any rule
- [principal predicates (IsAuthenticated / IsAnonymous / IsTheApp) and open reads](https://osysharp.com/reference/security/principal-predicates/) — the `user` a rule compares against
