Factories and Utilities Reference
Complete reference for all factory functions and utility helpers in the integration test infrastructure
Table of Contents
Overview
What are Factories?
Factories are functions that create database entities with sensible defaults. They handle:
- ✅ Node type creation
- ✅ Data initialization with defaults
- ✅ Relationship setup (edges)
- ✅ Permission grants
- ✅ ID generation
Location: tests/integration/setup/factories/
What are Utilities?
Utilities are functions that query or clean up database state. They handle:
- ✅ Common queries (access cache, permissions)
- ✅ Permission checks
- ✅ Cleanup operations
- ✅ Data introspection
Location: tests/integration/setup/utilities/
Import All at Once
// Import everything you need
import {
// Factories
createUserWithOrgSetup,
createResourceWithPermission,
setupTestWorkspace,
ensureNodeType,
// Utilities
queryUserAccessCache,
refreshUserAccessCache,
checkUserHasAccess,
cleanupTestData
} from '../setup/factories/database-fixtures';
import {
// Query helpers
queryUserAccessCache,
checkUserHasAccess,
getCurrentUserId,
// Cleanup helpers
cleanupTestData,
deleteNodesByType
} from '../setup/utilities';
// WorkOS factories
import {
createUserEvent,
createOrgEvent,
createMembershipEvent
} from '../setup/factories/workos-events';
Factory Functions
Database Fixture Factories
All functions in tests/integration/setup/factories/database-fixtures.ts
ensureNodeType
Ensure a node type exists in the database (idempotent).
Signature:
function ensureNodeType(
client: PoolClient,
nodeTypeId: string,
name?: string
): Promise<void>
Parameters:
client- Database clientnodeTypeId- ltree node type ID (e.g., 'test.resource')name- Optional human-readable name
Example:
await ensureNodeType(client, 'test.resource', 'Resource');
await ensureNodeType(client, 'platform.user'); // name derived as 'user'
When to use:
- Before creating nodes of a new type
- In test setup to ensure types exist
- Prefer
createNodeWithType()which calls this automatically
ensureNodeTypes
Ensure multiple node types exist (batch operation).
Signature:
function ensureNodeTypes(
client: PoolClient,
types: Array<[string, string?]>
): Promise<void>
Parameters:
client- Database clienttypes- Array of [nodeTypeId, name?] tuples
Example:
await ensureNodeTypes(client, [
['test.resource', 'Resource'],
['test.project', 'Project'],
['platform.team'] // name will be 'team'
]);
When to use:
- Setting up multiple node types at once
- Test initialization
- More efficient than multiple
ensureNodeType()calls
createResourceWithPermission
Create a resource node and grant permission to a user.
Signature:
function createResourceWithPermission(
client: PoolClient,
options: {
userNodeId: string;
resourceType?: string;
permissionLevel?: 'read' | 'write' | 'admin';
resourceData?: Record<string, unknown>;
externalId?: string;
}
): Promise<{
resourceId: string;
resourceNodeId: string;
permissionId: string;
}>
Parameters:
userNodeId- User node ID to grant permission to (required)resourceType- Resource node type (default: 'test.resource')permissionLevel- Permission level (default: 'read')resourceData- Resource data object (default: {})externalId- Optional external ID
Returns:
resourceId- External ID or node IDresourceNodeId- Database node IDpermissionId- Permission edge ID
Example:
const { resourceNodeId, permissionId } = await createResourceWithPermission(
client,
{
userNodeId: user.nodeId,
resourceType: 'test.document',
permissionLevel: 'write',
resourceData: {
title: 'My Document',
status: 'draft',
created_at: new Date().toISOString()
}
}
);
When to use:
- Testing permission grants
- Creating user-owned resources
- Permission verification tests
- Access control scenarios
createUserWithOrgSetup
Create a user with organization and optional team.
Signature:
function createUserWithOrgSetup(
client: PoolClient,
options?: {
userId?: string;
orgId?: string;
userData?: Record<string, unknown>;
orgData?: Record<string, unknown>;
includeTeam?: boolean;
teamName?: string;
}
): Promise<{
user: { id: string; nodeId: string; data: Record<string, unknown> };
org: { id: string; nodeId: string; data: Record<string, unknown> };
team?: { id: string; nodeId: string; data: Record<string, unknown> };
}>
Parameters:
userId- User external ID (auto-generated if not provided)orgId- Organization external ID (auto-generated if not provided)userData- User data overridesorgData- Organization data overridesincludeTeam- Whether to create a team (default: false)teamName- Team name if includeTeam is true (default: 'Test Team')
Returns:
user- User info (id, nodeId, data)org- Organization info (id, nodeId, data)team- Team info if includeTeam was true
Example:
// Basic usage
const { user, org } = await createUserWithOrgSetup(client);
// With custom data
const { user, org } = await createUserWithOrgSetup(client, {
userId: 'user_123',
orgId: 'org_456',
userData: {
email: 'john@example.com',
first_name: 'John',
last_name: 'Doe'
},
orgData: {
name: 'Acme Corp',
domains: ['acme.com']
}
});
// With team
const { user, org, team } = await createUserWithOrgSetup(client, {
includeTeam: true,
teamName: 'Engineering'
});
When to use:
- Most common starting point for tests
- User authentication tests
- Permission hierarchy tests
- Organization membership tests
setupTestWorkspace
Create a complete test workspace with user, org, optional team, and resources.
Signature:
function setupTestWorkspace(
client: PoolClient,
options?: {
resourceCount?: number;
resourceType?: string;
resourcePermission?: 'read' | 'write' | 'admin';
includeTeam?: boolean;
userData?: Record<string, unknown>;
orgData?: Record<string, unknown>;
}
): Promise<{
user: { id: string; nodeId: string; data: Record<string, unknown> };
org: { id: string; nodeId: string; data: Record<string, unknown> };
team?: { id: string; nodeId: string; data: Record<string, unknown> };
resources: Array<{ id: string; nodeId: string; permissionId: string }>;
}>
Parameters:
resourceCount- Number of resources to create (default: 0)resourceType- Type for created resources (default: 'test.resource')resourcePermission- Permission level for resources (default: 'read')includeTeam- Create a team (default: false)userData- User data overridesorgData- Organization data overrides
Returns:
user- User infoorg- Organization infoteam- Team info if includeTeam was trueresources- Array of created resources
Example:
const workspace = await setupTestWorkspace(client, {
includeTeam: true,
resourceCount: 5,
resourceType: 'test.document',
resourcePermission: 'write',
userData: { role: 'developer' },
orgData: { plan: 'enterprise' }
});
// Access created entities
console.log(workspace.user.id);
console.log(workspace.resources.length); // 5
When to use:
- Complex test scenarios
- Testing with multiple resources
- Performance testing
- End-to-end workflows
createNodeWithType
Create a node with automatic node type setup.
Signature:
function createNodeWithType(
client: PoolClient,
nodeType: string,
data?: Record<string, unknown>,
options?: {
externalId?: string;
typeName?: string;
}
): Promise<{
id: string;
nodeId: string;
data: Record<string, unknown>;
}>
Parameters:
client- Database clientnodeType- Node type IDdata- Node data (default: {})options.externalId- Optional external IDoptions.typeName- Optional type name
Returns:
id- External IDnodeId- Database node IDdata- Node data
Example:
const node = await createNodeWithType(client, 'test.document', {
title: 'My Document',
status: 'draft',
word_count: 1500
}, {
externalId: 'doc_123'
});
When to use:
- Creating custom node types
- Generic node creation
- When you need the node ID immediately
ensureEdgeType
Ensure an edge type exists in the database (idempotent).
Signature:
function ensureEdgeType(
client: PoolClient,
edgeTypeId: string,
name?: string
): Promise<void>
Parameters:
client- Database clientedgeTypeId- ltree edge type ID (e.g., 'test.links')name- Optional human-readable name
Example:
await ensureEdgeType(client, 'test.links', 'Links');
await ensureEdgeType(client, 'test.depends_on');
When to use:
- Before creating edges of a new type
- Prefer
createConnectedNodes()which calls this automatically
createConnectedNodes
Create two nodes connected by an edge.
Signature:
function createConnectedNodes(
client: PoolClient,
options: {
sourceType: string;
targetType: string;
edgeType: string;
sourceData?: Record<string, unknown>;
targetData?: Record<string, unknown>;
edgeData?: Record<string, unknown>;
}
): Promise<{
source: { id: string; nodeId: string };
target: { id: string; nodeId: string };
edge: { id: string };
}>
Parameters:
sourceType- Source node type (required)targetType- Target node type (required)edgeType- Edge type connecting them (required)sourceData- Source node datatargetData- Target node dataedgeData- Edge data
Returns:
source- Source node infotarget- Target node infoedge- Edge info
Example:
const { source, target, edge } = await createConnectedNodes(client, {
sourceType: 'test.person',
targetType: 'test.company',
edgeType: 'test.works_at',
sourceData: { name: 'Alice', title: 'Engineer' },
targetData: { name: 'Acme Corp', industry: 'Software' },
edgeData: { start_date: '2024-01-01' }
});
When to use:
- Testing graph relationships
- Edge traversal tests
- Relationship queries
- Graph structure tests
WorkOS Event Factories
All functions in tests/integration/setup/factories/workos-events.ts
createUserEvent
Create a WorkOS user event (user.created, user.updated, user.deleted).
Signature:
function createUserEvent(
type: 'user.created' | 'user.updated' | 'user.deleted',
userId: string,
overrides?: Partial<{
email: string;
firstName: string;
lastName: string;
emailVerified: boolean;
profilePictureUrl: string | null;
externalId: string | null;
metadata: Record<string, string>;
createdAt: string;
updatedAt: string;
lastSignInAt: string | null;
}>,
eventOverrides?: Partial<{
id: string;
createdAt: string;
}>
): Event
Example:
const event = createUserEvent('user.created', 'user_01234', {
email: 'john@example.com',
firstName: 'John',
lastName: 'Doe',
emailVerified: true
});
// Process webhook
await processUserWebhook(event);
When to use:
- Testing user webhook processing
- User sync scenarios
- User lifecycle events
createOrgEvent
Create a WorkOS organization event.
Signature:
function createOrgEvent(
type: 'organization.created' | 'organization.updated' | 'organization.deleted',
orgId: string,
overrides?: Partial<{
name: string;
allowProfilesOutsideOrganization: boolean;
domains: any[];
externalId: string | null;
metadata: Record<string, string>;
createdAt: string;
updatedAt: string;
}>,
eventOverrides?: Partial<{
id: string;
createdAt: string;
}>
): Event
Example:
const event = createOrgEvent('organization.created', 'org_01234', {
name: 'Acme Corporation',
domains: [{ domain: 'acme.com' }],
allowProfilesOutsideOrganization: false
});
When to use:
- Testing organization webhook processing
- Organization sync scenarios
- Domain verification tests
createMembershipEvent
Create a WorkOS membership event.
Signature:
function createMembershipEvent(
type: 'organization_membership.created' | 'organization_membership.updated' | 'organization_membership.deleted',
membershipId: string,
userId: string,
organizationId: string,
overrides?: Partial<{
organizationName: string;
status: 'active' | 'inactive';
role: { slug: string };
createdAt: string;
updatedAt: string;
}>,
eventOverrides?: Partial<{
id: string;
createdAt: string;
}>
): Event
Example:
const event = createMembershipEvent(
'organization_membership.created',
'membership_01234',
'user_01234',
'org_01234',
{
role: { slug: 'admin' },
status: 'active'
}
);
When to use:
- Testing membership webhooks
- Role assignment tests
- Membership lifecycle events
createRoleEvent
Create a WorkOS role event.
Signature:
function createRoleEvent(
type: 'role.created' | 'role.updated' | 'role.deleted',
roleSlug: string,
overrides?: Partial<{
permissions: string[];
createdAt: string;
updatedAt: string;
}>,
eventOverrides?: Partial<{
id: string;
createdAt: string;
}>
): Event
Example:
const event = createRoleEvent('role.created', 'engineer', {
permissions: ['read', 'write', 'deploy']
});
When to use:
- Testing role webhooks
- Permission mapping tests
- Role-based access control
Utility Functions
Query Helper Utilities
All functions in tests/integration/setup/utilities/query-helpers.ts
queryUserAccessCache
Query the user access cache for a specific user.
Signature:
function queryUserAccessCache(
client: PoolClient,
userId: string,
nodeId?: string
): Promise<CacheAccessRecord[]>
interface CacheAccessRecord {
user_external_id: string;
node_id: string;
min_permission: string;
depth: number;
paths?: string[];
}
Example:
// Query all access for user
const access = await queryUserAccessCache(client, 'user_123');
expect(access.length).toBeGreaterThan(0);
// Query specific node
const nodeAccess = await queryUserAccessCache(client, 'user_123', nodeId);
expect(nodeAccess[0]?.min_permission).toBe('auth.read');
refreshUserAccessCache
Refresh the user access cache for a specific user.
Signature:
function refreshUserAccessCache(
client: PoolClient,
userId: string
): Promise<boolean>
Example:
// Grant permission
await grantPermission(client, userNodeId, resourceNodeId, 'write');
// Refresh cache to pick up new permission
await refreshUserAccessCache(client, userId);
// Verify
const access = await queryUserAccessCache(client, userId, resourceNodeId);
expect(access.length).toBeGreaterThan(0);
checkUserHasAccess
Check if user has access to a node.
Signature:
function checkUserHasAccess(
client: PoolClient,
nodeId: string
): Promise<boolean>
Example:
await asUser(userId, orgId, async (client) => {
const hasAccess = await checkUserHasAccess(client, resourceId);
expect(hasAccess).toBe(true);
});
checkUserCanManage
Check if user can manage a node (write permission).
Signature:
function checkUserCanManage(
client: PoolClient,
nodeId: string
): Promise<boolean>
Example:
const canManage = await checkUserCanManage(client, resourceId);
expect(canManage).toBe(true);
checkUserIsAdmin
Check if user is admin for a node.
Signature:
function checkUserIsAdmin(
client: PoolClient,
nodeId: string
): Promise<boolean>
Example:
const isAdmin = await checkUserIsAdmin(client, resourceId);
expect(isAdmin).toBe(false);
checkPermissionSatisfies
Check if one permission satisfies another (hierarchy check).
Signature:
function checkPermissionSatisfies(
client: PoolClient,
granted: string,
required: string
): Promise<boolean>
Example:
// Check if admin satisfies read
const satisfies = await checkPermissionSatisfies(
client,
'auth.read.write.admin',
'auth.read'
);
expect(satisfies).toBe(true);
getCurrentUserId
Get the current session user ID.
Signature:
function getCurrentUserId(
client: PoolClient
): Promise<string | null>
Example:
await asUser(userId, orgId, async (client) => {
const currentId = await getCurrentUserId(client);
expect(currentId).toBe(userId);
});
getCurrentRole
Get the current database role.
Signature:
function getCurrentRole(
client: PoolClient
): Promise<string>
Example:
const role = await getCurrentRole(client);
expect(role).toBe('studio_user');
countRecords
Count records in a table with optional filter.
Signature:
function countRecords(
client: PoolClient,
tableName: string,
whereClause?: string,
params?: unknown[]
): Promise<number>
Example:
const count = await countRecords(
client,
'auth.user_access_cache',
'user_external_id = $1',
['user_123']
);
functionExists
Check if a database function exists.
Signature:
function functionExists(
client: PoolClient,
functionName: string,
schema?: string
): Promise<boolean>
Example:
const exists = await functionExists(client, 'user_has_access', 'auth');
expect(exists).toBe(true);
roleExists
Check if a database role exists.
Signature:
function roleExists(
client: PoolClient,
roleName: string
): Promise<boolean>
Example:
const exists = await roleExists(client, 'studio_user');
expect(exists).toBe(true);
getNodesByType
Get all nodes of a specific type.
Signature:
function getNodesByType(
client: PoolClient,
nodeTypeId: string
): Promise<QueryResult['rows']>
Example:
const nodes = await getNodesByType(client, 'platform.user');
expect(nodes.length).toBeGreaterThan(0);
getEdgesByType
Get all edges of a specific type.
Signature:
function getEdgesByType(
client: PoolClient,
edgeTypeId: string
): Promise<QueryResult['rows']>
Example:
const edges = await getEdgesByType(client, 'auth.read');
expect(edges.length).toBeGreaterThan(0);
getEdgesBetweenNodes
Get edges between two nodes.
Signature:
function getEdgesBetweenNodes(
client: PoolClient,
sourceNodeId: string,
targetNodeId: string,
edgeTypeId?: string
): Promise<QueryResult['rows']>
Example:
const edges = await getEdgesBetweenNodes(
client,
userNodeId,
orgNodeId,
'auth.read.member'
);
expect(edges.length).toBe(1);
Cleanup Helper Utilities
All functions in tests/integration/setup/utilities/cleanup-helpers.ts
deleteNodesByType
Delete all nodes of a specific type.
Signature:
function deleteNodesByType(
client: PoolClient,
nodeTypeId: string
): Promise<number>
Example:
const deleted = await deleteNodesByType(client, 'test.resource');
console.log(`Deleted ${deleted} nodes`);
deleteEdgesByType
Delete all edges of a specific type.
Signature:
function deleteEdgesByType(
client: PoolClient,
edgeTypeId: string
): Promise<number>
Example:
const deleted = await deleteEdgesByType(client, 'auth.read');
deleteEdge
Delete a specific edge by ID.
Signature:
function deleteEdge(
client: PoolClient,
edgeId: string
): Promise<boolean>
Example:
const deleted = await deleteEdge(client, edgeId);
expect(deleted).toBe(true);
deleteNode
Delete a specific node by ID.
Signature:
function deleteNode(
client: PoolClient,
nodeId: string
): Promise<boolean>
Example:
const deleted = await deleteNode(client, nodeId);
expect(deleted).toBe(true);
deleteEdgesBetweenNodes
Delete edges between two nodes.
Signature:
function deleteEdgesBetweenNodes(
client: PoolClient,
sourceNodeId: string,
targetNodeId: string,
edgeTypeId?: string
): Promise<number>
Example:
const deleted = await deleteEdgesBetweenNodes(
client,
userNodeId,
orgNodeId,
'auth.read.member'
);
clearUserAccessCache
Clear user access cache for a specific user.
Signature:
function clearUserAccessCache(
client: PoolClient,
userId: string
): Promise<number>
Example:
const deleted = await clearUserAccessCache(client, 'user_123');
clearAllUserAccessCache
Clear all user access cache entries (use with caution).
Signature:
function clearAllUserAccessCache(
client: PoolClient
): Promise<number>
Example:
await clearAllUserAccessCache(client);
cleanupByExternalIdPattern
Cleanup test data by external ID pattern.
Signature:
function cleanupByExternalIdPattern(
client: PoolClient,
pattern: string
): Promise<number>
Example:
// Clean up all test IDs starting with 'test_'
const deleted = await cleanupByExternalIdPattern(client, 'test_%');
cleanupTestData
Delete test nodes and edges (composite cleanup).
Signature:
function cleanupTestData(
client: PoolClient,
options: {
nodeTypes?: string[];
edgeTypes?: string[];
externalIdPattern?: string;
}
): Promise<{ nodes: number; edges: number }>
Example:
const result = await cleanupTestData(client, {
nodeTypes: ['test.resource', 'test.document'],
edgeTypes: ['test.links'],
externalIdPattern: 'test_%'
});
console.log(`Deleted ${result.nodes} nodes, ${result.edges} edges`);
truncateTable
Truncate a table (removes all rows).
Signature:
function truncateTable(
client: PoolClient,
tableName: string,
cascade?: boolean
): Promise<boolean>
Example:
await truncateTable(client, 'test_schema.test_table', true);
Decision Guide
When to Use What?
Creating Data
| Scenario | Tool | Example |
|---|---|---|
| Create user + org | createUserWithOrgSetup() |
Most tests start here |
| Create resource with permission | createResourceWithPermission() |
Permission tests |
| Create complete workspace | setupTestWorkspace() |
Complex scenarios |
| Create custom node | createNodeWithType() |
Generic nodes |
| Create connected nodes | createConnectedNodes() |
Graph relationships |
Querying Data
| Scenario | Tool | Example |
|---|---|---|
| Check user access | queryUserAccessCache() |
After granting permission |
| Verify permission level | checkUserHasAccess() |
In user context |
| Check management rights | checkUserCanManage() |
Before updates |
| Check admin rights | checkUserIsAdmin() |
Before deletes |
| Get current user | getCurrentUserId() |
Verify context |
| Find nodes by type | getNodesByType() |
Search operations |
Cleaning Up
| Scenario | Tool | Example |
|---|---|---|
| Clean specific types | deleteNodesByType() |
Targeted cleanup |
| Clean test pattern | cleanupByExternalIdPattern() |
Pattern-based |
| Comprehensive cleanup | cleanupTestData() |
Composite cleanup |
| Clear cache | clearUserAccessCache() |
Cache invalidation |
| Delete specific entity | deleteNode() / deleteEdge() |
Single entity |
Migration Patterns
Before: Manual Node Creation
❌ Old Pattern:
await client.query(`
INSERT INTO graph.node_types (id, name)
VALUES ('test.resource'::ltree, 'Resource')
ON CONFLICT (id) DO NOTHING
`);
const result = await client.query(`
INSERT INTO graph.nodes (node_type_id, external_id, data)
VALUES ('test.resource'::ltree, $1, $2)
RETURNING id
`, [resourceId, { name: 'Resource' }]);
const nodeId = result.rows[0].id;
✅ New Pattern:
const { nodeId } = await createNodeWithType(client, 'test.resource', {
name: 'Resource'
}, { externalId: resourceId });
Before: Manual Permission Grant
❌ Old Pattern:
// Create node
const nodeResult = await client.query(`...`);
const nodeId = nodeResult.rows[0].id;
// Create permission edge
await client.query(`
INSERT INTO graph.edge (edge_type_id, source_node_id, target_node_id, data)
VALUES ('auth.read'::ltree, $1, $2, '{}')
`, [userNodeId, nodeId]);
// Refresh cache
await client.query('SELECT auth.refresh_user_access_cache($1)', [userId]);
✅ New Pattern:
const { resourceNodeId } = await createResourceWithPermission(client, {
userNodeId,
permissionLevel: 'read'
});
await refreshUserAccessCache(client, userId);
Before: Manual User Setup
❌ Old Pattern:
// Insert node types
await client.query(`...node types...`);
// Create user node
const userResult = await client.query(`...insert user...`);
const userNodeId = userResult.rows[0].id;
// Create org node
const orgResult = await client.query(`...insert org...`);
const orgNodeId = orgResult.rows[0].id;
// Create membership edge
await client.query(`...insert edge...`);
✅ New Pattern:
const { user, org } = await createUserWithOrgSetup(client, {
userId: 'user_123',
orgId: 'org_456'
});
Before: Manual Cleanup
❌ Old Pattern:
await client.query('DELETE FROM graph.edge WHERE edge_type_id = $1::ltree', ['test.links']);
await client.query('DELETE FROM graph.nodes WHERE node_type_id = $1::ltree', ['test.resource']);
await client.query('DELETE FROM auth.user_access_cache WHERE user_external_id LIKE $1', ['test_%']);
✅ New Pattern:
const result = await cleanupTestData(client, {
nodeTypes: ['test.resource'],
edgeTypes: ['test.links'],
externalIdPattern: 'test_%'
});
Before: Manual Cache Query
❌ Old Pattern:
const result = await client.query(`
SELECT * FROM auth.user_access_cache
WHERE user_external_id = $1 AND node_id = $2
`, [userId, nodeId]);
const access = result.rows;
✅ New Pattern:
const access = await queryUserAccessCache(client, userId, nodeId);
Common Workflows
Complete Test Workflow
Here's a complete example combining factories, utilities, and best practices:
import { describe, expect } from 'vitest';
import test from '../test';
import { generateTestId } from '@/test-utils-integration/utils/graph-fixtures';
import {
createUserWithOrgSetup,
createResourceWithPermission,
setupTestWorkspace
} from '../setup/factories/database-fixtures';
import {
queryUserAccessCache,
refreshUserAccessCache,
checkUserHasAccess,
cleanupTestData
} from '../setup/utilities';
describe('Document Sharing', () => {
test('user can share document with team member', async ({ asSystem, asUser }) => {
let owner: any;
let member: any;
let documentId: string;
// Setup: Create test environment
await asSystem(async (client) => {
// Create owner with organization
owner = await createUserWithOrgSetup(client, {
userId: generateTestId('owner'),
orgId: generateTestId('org'),
userData: { role: 'owner' }
});
// Create team member in same org
member = await createUserWithOrgSetup(client, {
userId: generateTestId('member'),
orgId: owner.org.id, // Same org
userData: { role: 'member' }
});
// Owner creates a document
const { resourceNodeId } = await createResourceWithPermission(client, {
userNodeId: owner.user.nodeId,
resourceType: 'test.document',
permissionLevel: 'admin',
resourceData: {
title: 'Shared Document',
content: 'Secret content'
}
});
documentId = resourceNodeId;
// Refresh caches
await refreshUserAccessCache(client, owner.user.id);
await refreshUserAccessCache(client, member.user.id);
});
// Test: Owner can access document
await asUser(owner.user.id, owner.org.id, async (client) => {
const hasAccess = await checkUserHasAccess(client, documentId);
expect(hasAccess).toBe(true);
});
// Test: Member initially cannot access
await asUser(member.user.id, member.org.id, async (client) => {
const hasAccess = await checkUserHasAccess(client, documentId);
expect(hasAccess).toBe(false);
});
// Action: Owner shares with member
await asSystem(async (client) => {
await createResourceWithPermission(client, {
userNodeId: member.user.nodeId,
resourceType: 'test.document',
permissionLevel: 'read',
resourceData: {}, // Existing document
externalId: documentId
});
await refreshUserAccessCache(client, member.user.id);
});
// Verify: Member can now access
await asUser(member.user.id, member.org.id, async (client) => {
const hasAccess = await checkUserHasAccess(client, documentId);
expect(hasAccess).toBe(true);
// Verify permission level
const access = await queryUserAccessCache(client, member.user.id, documentId);
expect(access[0]?.min_permission).toBe('auth.read');
});
// Cleanup
await asSystem(async (client) => {
await cleanupTestData(client, {
nodeTypes: ['platform.user', 'platform.organization', 'test.document'],
edgeTypes: ['auth.read', 'auth.read.member'],
externalIdPattern: 'test_%'
});
});
});
});
Permission Hierarchy Workflow
Testing transitive permissions through organizational hierarchy:
test('permissions flow through org hierarchy', async ({ asSystem }) => {
await asSystem(async (client) => {
// Create complete workspace
const workspace = await setupTestWorkspace(client, {
includeTeam: true,
resourceCount: 0 // We'll create resources manually
});
// Create resource owned by org
const { resourceNodeId } = await createResourceWithPermission(client, {
userNodeId: workspace.org.nodeId, // Grant to org
resourceType: 'test.project',
permissionLevel: 'write',
resourceData: { name: 'Company Project' }
});
// Refresh user cache
await refreshUserAccessCache(client, workspace.user.id);
// User inherits permission through org membership
const access = await queryUserAccessCache(
client,
workspace.user.id,
resourceNodeId
);
expect(access.length).toBeGreaterThan(0);
expect(access[0].depth).toBeGreaterThan(0); // Transitive
expect(access[0].min_permission).toMatch(/auth\.read\.write/);
// Cleanup
await cleanupTestData(client, {
nodeTypes: ['platform.user', 'platform.organization', 'platform.team', 'test.project'],
edgeTypes: ['auth.read.member', 'auth.read.write']
});
});
});
WorkOS Webhook Workflow
Testing complete webhook processing flow:
import { createUserEvent, createOrgEvent, createMembershipEvent } from '../setup/factories/workos-events';
test('process complete user onboarding', async ({ asSystem }) => {
const userId = 'user_01234';
const orgId = 'org_01234';
const membershipId = 'membership_01234';
// Step 1: Organization created
const orgEvent = createOrgEvent('organization.created', orgId, {
name: 'Acme Corp',
domains: [{ domain: 'acme.com' }]
});
await processWebhook(orgEvent);
// Step 2: User created
const userEvent = createUserEvent('user.created', userId, {
email: 'john@acme.com',
firstName: 'John',
lastName: 'Doe'
});
await processWebhook(userEvent);
// Step 3: Membership created
const membershipEvent = createMembershipEvent(
'organization_membership.created',
membershipId,
userId,
orgId,
{ role: { slug: 'admin' } }
);
await processWebhook(membershipEvent);
// Verify complete state
await asSystem(async (client) => {
// Verify user exists
const users = await getNodesByType(client, 'platform.user');
const user = users.find(u => u.external_id === userId);
expect(user).toBeDefined();
expect(user.data.email).toBe('john@acme.com');
// Verify org exists
const orgs = await getNodesByType(client, 'platform.organization');
const org = orgs.find(o => o.external_id === orgId);
expect(org).toBeDefined();
expect(org.data.name).toBe('Acme Corp');
// Verify membership edge
const edges = await getEdgesBetweenNodes(
client,
user.id,
org.id,
'auth.read.member'
);
expect(edges.length).toBe(1);
// Verify user has access to org
await refreshUserAccessCache(client, userId);
const access = await queryUserAccessCache(client, userId, org.id);
expect(access.length).toBeGreaterThan(0);
});
});
Bulk Operations Workflow
Testing scenarios with many resources:
test('user manages multiple projects', async ({ asSystem }) => {
await asSystem(async (client) => {
// Create workspace with many resources
const workspace = await setupTestWorkspace(client, {
resourceCount: 10,
resourceType: 'test.project',
resourcePermission: 'admin'
});
expect(workspace.resources).toHaveLength(10);
// Refresh cache
await refreshUserAccessCache(client, workspace.user.id);
// Verify all resources accessible
const access = await queryUserAccessCache(client, workspace.user.id);
expect(access.length).toBeGreaterThanOrEqual(10);
// Verify admin permission on all
const projectAccess = access.filter(a =>
workspace.resources.some(r => r.nodeId === a.node_id)
);
projectAccess.forEach(a => {
expect(a.min_permission).toBe('auth.read.write.admin');
});
// Cleanup all at once
await cleanupTestData(client, {
nodeTypes: ['test.project', 'platform.user', 'platform.organization'],
externalIdPattern: 'test_%'
});
});
});
Complex Graph Relationship Workflow
Testing interconnected nodes:
test('complex graph relationships', async ({ asSystem }) => {
await asSystem(async (client) => {
// Create person-company-project relationships
const { source: person, target: company, edge: employment } =
await createConnectedNodes(client, {
sourceType: 'test.person',
targetType: 'test.company',
edgeType: 'test.works_at',
sourceData: { name: 'Alice', title: 'Engineer' },
targetData: { name: 'Acme Corp', industry: 'Software' }
});
const { source: companyNode, target: project } =
await createConnectedNodes(client, {
sourceType: 'test.company',
targetType: 'test.project',
edgeType: 'test.owns',
sourceData: { id: company.id }, // Link to existing company
targetData: { name: 'Project X', status: 'active' }
});
// Verify relationships
const employmentEdges = await getEdgesBetweenNodes(
client,
person.nodeId,
company.nodeId,
'test.works_at'
);
expect(employmentEdges).toHaveLength(1);
const ownershipEdges = await getEdgesBetweenNodes(
client,
company.nodeId,
project.nodeId,
'test.owns'
);
expect(ownershipEdges).toHaveLength(1);
// Cleanup
await cleanupTestData(client, {
nodeTypes: ['test.person', 'test.company', 'test.project'],
edgeTypes: ['test.works_at', 'test.owns']
});
});
});
Quick Reference Card
Most Used Factories
// User + Org (most common start)
const { user, org } = await createUserWithOrgSetup(client);
// Resource with permission
const { resourceNodeId } = await createResourceWithPermission(client, {
userNodeId: user.nodeId,
permissionLevel: 'write'
});
// Complete workspace
const workspace = await setupTestWorkspace(client, {
includeTeam: true,
resourceCount: 5
});
Most Used Utilities
// Query access cache
const access = await queryUserAccessCache(client, userId);
const nodeAccess = await queryUserAccessCache(client, userId, nodeId);
// Refresh cache
await refreshUserAccessCache(client, userId);
// Check permissions
const hasAccess = await checkUserHasAccess(client, nodeId);
const canManage = await checkUserCanManage(client, nodeId);
const isAdmin = await checkUserIsAdmin(client, nodeId);
// Cleanup
await cleanupTestData(client, {
nodeTypes: ['test.resource'],
externalIdPattern: 'test_%'
});
Common Imports
import test from '../test';
import { generateTestId } from '@/test-utils-integration/utils/graph-fixtures';
import {
createUserWithOrgSetup,
createResourceWithPermission,
setupTestWorkspace
} from '../setup/factories/database-fixtures';
import {
queryUserAccessCache,
refreshUserAccessCache,
checkUserHasAccess,
cleanupTestData
} from '../setup/utilities';
Next Steps:
- Integration Test Guide - Learn how to use these in tests
- Common Scenarios - See real examples
- Migration Guide - Migrate existing tests