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.