Skip to content

Auth and actors

Actors let request and browser helpers resolve auth state from a named role in the current world.

In Sounding terms:

  • the trial states the behavior
  • the world provides the situation
  • the actor gives the role inside that situation

What an actor is

An actor is the role a trial operates through inside a world.

Examples:

  • guest
  • subscriber
  • publisher
  • unlockedReader
  • teamOwner

Actors usually come from a scenario:

js
const current = await sails.sounding.world.use('issue-access')

Then the trial can reference them clearly:

  • current.users.subscriber
  • current.users.publisher
  • current.users.unlockedReader

request.as(actor)

When a request trial needs to act as an authenticated actor, load a world on the test declaration and use request.as(actor).

js
test(
  'subscriber can read the issue',
  { world: 'issue-access' },
  async ({ world, request, expect }) => {
    const response = await request
      .as('subscriber')
      .get(`/i/${world.current.issues.gatedIssue.slug}`)

    expect(response).toHaveStatus(200)
  }
)

That keeps the scenario name in the trial declaration and lets the request body read like product behavior.

You can also pass the resolved actor object directly:

js
test('subscriber can read the issue', async ({ world, request, expect }) => {
  const current = await world.use('issue-access')

  const response = await request
    .as(current.users.subscriber)
    .get(`/i/${current.issues.gatedIssue.slug}`)

  expect(response).toHaveStatus(200)
})

This lets request trials act as an authenticated user without manual session setup.

Sounding resolves common auth conventions such as:

  • User with req.session.userId
  • Creator with req.session.creatorId
  • explicit overrides in config/sounding.js when an app uses custom naming

request.as() also accepts an email address when the configured auth model can resolve an existing actor:

js
const response = await request.as('[email protected]').get('/me')

For Inertia contract tests, visit.as('subscriber') applies the same actor resolution before making the visit.

login.as(actorOrEmail, page)

For browser-capable trials, Sounding gives you a higher-level login helper.

js
test(
  'subscriber can finish the issue',
  { browser: true },
  async ({ sails, login, page, expect }) => {
    const current = await sails.sounding.world.use('issue-access')

    await login.as('subscriber', page)
    await page.goto(`/i/${current.issues.gatedIssue.slug}`)

    await expect(
      page.getByText(current.issues.gatedIssue.premiumDetail)
    ).toBeVisible()
  }
)

Useful details:

  • if you pass an actor name like 'subscriber', Sounding will look for it in world.current.users
  • if you pass an email address, Sounding will resolve or create that actor as needed
  • the default browser login path currently uses a magic-link flow under the hood

If an actor alias cannot be resolved, Sounding lists the actor names available in the current world. That makes typo-level failures point back to product language instead of leaving you to inspect the scenario by hand.

login.withPassword(actorOrEmail, page, options)

For apps that use real password login forms, Sounding should let the browser follow that flow instead of faking session state.

js
test(
  'creator reaches invoices after password login',
  { browser: true },
  async ({ login, page, expect }) => {
    await login.withPassword('[email protected]', page, {
      password: 'secret123',
      returnUrl: '/invoices'
    })

    await expect(page).toHaveURL(/\/invoices$/)
  }
)

This helper should:

  • load the configured login page
  • fill the app's real email and password fields
  • respect extras like rememberMe and returnUrl
  • work for both User and Creator style apps

The auth helper surface

The top-level auth object is the lower-level auth API.

Today it includes:

  • auth.resolveActor(actorOrEmail)
  • auth.issueMagicLink(actorOrEmail)
  • auth.requestMagicLink(actorOrEmail)
  • auth.request.withPassword(actorOrEmail, options)
  • auth.login.as(actorOrEmail, page)
  • auth.login.withPassword(actorOrEmail, page, options)

auth.resolveActor()

Use this when you need a real actor record and want Sounding to resolve either:

  • a world actor name
  • a user or creator object
  • an email address

When a world actor name is missing, the error includes the available aliases from common actor collections such as users and creators.

Use this when you want the magic-link token and URL directly.

js
const link = await auth.issueMagicLink('[email protected]')
await page.goto(link.url)

Use this when you want to exercise the actual app flow that requests a magic link and sends mail.

js
test('requesting a magic link sends a usable email', async ({
  auth,
  mailbox,
  expect
}) => {
  const result = await auth.requestMagicLink('[email protected]')
  const email = mailbox.latest()

  expect(result.response).toHaveStatus(302)
  expect(email.ctaUrl).toContain('/magic-link/')
})

auth.request.withPassword()

Use this when you want to exercise the app's real password login action at the request layer.

js
test('creator password login redirects to invoices', async ({
  auth,
  expect
}) => {
  const result = await auth.request.withPassword('[email protected]', {
    password: 'secret123',
    returnUrl: '/invoices'
  })

  expect(result.response).toHaveStatus(302)
  expect(result.response).toRedirectTo('/invoices')
})

A common pattern for actor-driven tests

A common flow is:

  1. load a world
  2. pick an actor from the world
  3. use that actor through request.as(), visit.as(), or login.as()
js
test('publisher can create an issue', async ({ sails, expect }) => {
  const current = await sails.sounding.world.use('publisher-editor')

  const response = await sails.sounding.request
    .as(current.users.publisher)
    .post('/issues', {
      title: 'New issue'
    })

  expect(response).toHaveStatus(200)
})

When to use actor names vs explicit emails

Use an actor name when the user is part of a named business situation:

  • 'subscriber'
  • 'publisher'
  • 'unlockedReader'

Use an explicit email when the trial is really about the auth path itself and you want to name the identity directly:

The main rule

Prefer actor names for named roles in a world. Use explicit emails when the test is about a specific identity.

A Sounding auth test should read like:

  • subscriber can open the issue
  • publisher can save the draft
  • reader receives a magic link

not like:

  • user 3 with session state x can hit route y

All open source projects are released under the MIT License.