# Testing Validation and Access Control https://www.sanity.io/learn/course/testing-sanity-studio/testing-validation-and-access-control.md Start with the simplest testing scenario: functions with no external dependencies or side effects. Test access control logic that determines who can edit fields, validation functions that enforce business rules, and utility functions that transform data. These isolated functions are straightforward to test and often contain critical business logic that protects content quality across your organization. In this lesson, you'll test validation logic and access control rules from the events Studio—functions with no external dependencies that are straightforward to test. ## Why start with isolated functions? A "pure" function is predictable and isolated: * **Same inputs always produce same outputs** - No randomness or hidden state * **No side effects** - Doesn't modify external state, make API calls, or change files * **Easy to test** - Pass inputs, verify outputs, done Functions with no external dependencies are the easiest place to start testing because they require no mocks, no setup, and no teardown. The function is completely self-contained. ## Testing validation and access control The Studio includes access control logic that determines who can edit certain fields. The slug field has a rule: anyone can set the initial slug, but only administrators can change it once set: ```typescript:apps/studio/helpers.ts import type {CurrentUser} from 'sanity' /** * Determines if the current user can edit a slug field * Only administrators can edit existing slugs */ export function canEditSlug(user?: Omit | null): boolean { return user?.roles.some((role) => role.name === 'administrator') ?? false } ``` 1. Learn more about Sanity's role-based access control in [Users, roles and using roles](https://www.sanity.io/learn/course/introduction-to-users-and-roles) This function is pure: given a user object, it returns whether they have admin privileges. No API calls, no state changes, no external dependencies. To ensure this pure helper function matches our business logic, we should test the following scenarios: 1. **Admin user** - Has administrator role (should return `true`) 2. **Regular user** - Has non-admin role like editor (should return `false`) 3. **Multiple roles** - User with both editor and admin roles (should return `true`) 4. **Null user** - No user logged in (should return `false`) 5. **Empty roles** - User exists but has no roles assigned (should return `false`) ```typescript:apps/studio/helpers.test.ts import {describe, it, expect} from 'vitest' import type {CurrentUser} from 'sanity' import {canEditSlug} from './helpers' describe('canEditSlug', () => { it('allows administrators to edit slugs', () => { const adminUser: Omit = { id: 'admin-user', name: 'Admin User', email: 'admin@example.com', roles: [{name: 'administrator', title: 'Administrator'}], } expect(canEditSlug(adminUser)).toBe(true) }) it('prevents non-admin users from editing slugs', () => { const regularUser: Omit = { id: 'regular-user', name: 'Regular User', email: 'user@example.com', roles: [{name: 'editor', title: 'Editor'}], } expect(canEditSlug(regularUser)).toBe(false) }) it('handles users with multiple roles', () => { const multiRoleUser: Omit = { id: 'multirole-user', name: 'Multi-role User', email: 'multi@example.com', roles: [ {name: 'editor', title: 'Editor'}, {name: 'administrator', title: 'Administrator'}, ], } expect(canEditSlug(multiRoleUser)).toBe(true) }) it('prevents access when user is `null`', () => { expect(canEditSlug(null)).toBe(false) }) it('prevents access when user has no roles', () => { const userWithoutRoles: Omit = { id: 'no-roles', name: 'No Roles', email: 'noroles@example.com', roles: [], } expect(canEditSlug(userWithoutRoles)).toBe(false) }) }) ``` Run the tests from the root with `pnpm test`. All tests should pass. This approach ensures the permission check works correctly for all possible user states, protecting your content from unauthorized edits. ## Testing validation and access control Another example might be the IANA tags that you might use to localize your content: ```typescript:apps/studio/validation.ts import type {StringRule} from 'sanity' /** * IANA language tag pattern (BCP 47) * @see https://en.wikipedia.org/wiki/IETF_language_tag * * Supports formats like: * - en (2-letter language code) * - en-US (language + region) * - zh-Hant-TW (language + script + region) * - en-US-x-private (with private use extensions) */ export const LANGUAGE_TAG_PATTERN = /^[a-z]{2,3}(?:-[A-Z][a-z]{3})?(?:-(?:[A-Z]{2}|\d{3}))?(?:-[a-zA-Z0-9]{5,8}|-[0-9][a-zA-Z0-9]{3})*$/ /** * Validation function for IANA language tags (BCP 47 format) * * @example * ```ts * defineField({ * name: 'language', * type: 'string', * validation: validateLanguageTag * }) * ``` */ export const validateLanguageTag = (rule: StringRule): StringRule => rule.regex(LANGUAGE_TAG_PATTERN, { name: 'IANA language tag', }) ``` 1. This example uses [TSDoc](https://typedoc.org/)-style comments to annotate and provide at-a-glance, inline documentation when hovering over definitions. Our test suite for this validation might look something like: ```typescript:apps/studio/validation.test.ts import {describe, it, expect} from 'vitest' import {LANGUAGE_TAG_PATTERN} from './validation' describe('LANGUAGE_TAG_PATTERN', () => { it('matches valid language tags (real-world examples)', () => { // Simple language codes (most common) expect(LANGUAGE_TAG_PATTERN.test('en')).toBe(true) // English expect(LANGUAGE_TAG_PATTERN.test('fr')).toBe(true) // French expect(LANGUAGE_TAG_PATTERN.test('ja')).toBe(true) // Japanese expect(LANGUAGE_TAG_PATTERN.test('nan')).toBe(true) // Min Nan Chinese (3-letter ISO 639-3) // Language + Region (localization) expect(LANGUAGE_TAG_PATTERN.test('en-US')).toBe(true) // US English expect(LANGUAGE_TAG_PATTERN.test('en-GB')).toBe(true) // British English expect(LANGUAGE_TAG_PATTERN.test('fr-CA')).toBe(true) // Canadian French expect(LANGUAGE_TAG_PATTERN.test('es-419')).toBe(true) // Latin American Spanish (UN M.49 numeric code) }) it('rejects common mistakes', () => { // Case errors (most common mistake) expect(LANGUAGE_TAG_PATTERN.test('EN')).toBe(false) // Language must be lowercase expect(LANGUAGE_TAG_PATTERN.test('en-us')).toBe(false) // Region must be uppercase (en-US) expect(LANGUAGE_TAG_PATTERN.test('zh-hant')).toBe(false) // Script must be Title Case (zh-Hant) expect(LANGUAGE_TAG_PATTERN.test('zh-HANT')).toBe(false) // Script cannot be all caps // Wrong separator expect(LANGUAGE_TAG_PATTERN.test('en_US')).toBe(false) // Must use hyphen, not underscore expect(LANGUAGE_TAG_PATTERN.test('en.US')).toBe(false) // Must use hyphen, not period // Using full names instead of codes expect(LANGUAGE_TAG_PATTERN.test('english')).toBe(false) // Must use ISO code 'en', not full name expect(LANGUAGE_TAG_PATTERN.test('English')).toBe(false) // Wrong region code length expect(LANGUAGE_TAG_PATTERN.test('en-USA')).toBe(false) // Region must be 2 letters, not 3 expect(LANGUAGE_TAG_PATTERN.test('en-U')).toBe(false) // Region cannot be 1 letter // Confusing region with script expect(LANGUAGE_TAG_PATTERN.test('zh-CN')).toBe(true) // Valid but ambiguous - prefer zh-Hans expect(LANGUAGE_TAG_PATTERN.test('zh-TW')).toBe(true) // Valid but ambiguous - prefer zh-Hant }) }) ``` These functions exhibit a key trait: they're completely isolated. No API calls, no database queries, no React hooks. This makes them fast to test and easy to verify. Pure function tests require no special setup—no providers, no mocks, no configuration. This simplicity makes them an excellent starting point for your testing strategy. More importantly though, these pure functions often form the foundation of critical business rules in your Sanity Studio. Validation functions ensure data integrity, formatting utilities maintain consistency, and helper functions encapsulate important domain logic. By thoroughly testing these functions, you're safeguarding the core rules that protect your content quality. ## Next steps You've tested validation and access control logic—permission logic with edge cases like admin checks and null handling and IANA tag validation. Next you'll test functions that need more context—validation rules that query Sanity's Content Lake to enforce business logic. You'll learn to mock Sanity client, create reusable fixtures, and build a testing harness for async validation logic.