n8n/packages/testing/playwright/services/user-api-helper.ts
2026-03-17 15:58:47 +00:00

99 lines
3.0 KiB
TypeScript

import type { User } from '@n8n/api-types';
import { customAlphabet } from 'nanoid';
import type { ApiHelpers } from './api-helper';
import { TestError } from '../Types';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz0123456789', 8);
export interface TestUser {
id: string;
email: string;
password: string;
firstName: string;
lastName: string;
role: 'global:owner' | 'global:admin' | 'global:member';
}
/**
* Creates test users via n8n's invitation API.
* Note: Using this with n8n.api will affect browser cookies. Use with the isolated api fixture instead unless you want to overwrite the existing user
*/
export class UserApiHelper {
constructor(private api: ApiHelpers) {}
/**
* Create and activate a test user
*/
async create(options: Partial<TestUser> = {}): Promise<TestUser> {
const user = {
email: options.email?.toLowerCase() ?? `testuser${nanoid()}@test.com`,
password: options.password ?? 'PlaywrightTest123',
firstName: options.firstName ?? 'Test',
lastName: options.lastName ?? `User${nanoid()}`,
role: options.role ?? 'global:member',
};
// Invite user
const inviteResponse = await this.api.request.post('/rest/invitations', {
data: [{ email: user.email, role: user.role }],
});
if (!inviteResponse.ok()) {
throw new TestError(`Failed to invite user: ${inviteResponse.status()}`);
}
const inviteData = await inviteResponse.json();
const { id, inviteAcceptUrl } = inviteData.data[0].user;
// Accept invitation (invite URL contains token, e.g. /signup?token=...)
const url = new URL(inviteAcceptUrl);
const token = url.searchParams.get('token');
if (!token) {
throw new TestError(`Invite URL has no token: ${inviteAcceptUrl}`);
}
const acceptResponse = await this.api.request.post('/rest/invitations/accept', {
data: {
token,
firstName: user.firstName,
lastName: user.lastName,
password: user.password,
},
});
if (!acceptResponse.ok()) {
throw new TestError(`Failed to accept invitation: ${acceptResponse.status()}`);
}
return { id, ...user };
}
/**
* Get all users, with optional filtering by email, firstName, lastName, or fullText search
*/
async getUsers(options?: {
filter?: { email?: string; firstName?: string; lastName?: string; fullText?: string };
}): Promise<User[]> {
const params = new URLSearchParams();
if (options?.filter) {
params.set('filter', JSON.stringify(options.filter));
}
const response = await this.api.request.get('/rest/users', { params });
if (!response.ok()) {
throw new TestError(`Failed to get users: ${response.status()}`);
}
const json = await response.json();
// API returns { data: { count, items: [...users] } }
return json.data?.items ?? [];
}
/**
* Get a single user by email address
* @param email - The email address to search for
* @returns User object if found, null if no user exists with that email
*/
async getUserByEmail(email: string): Promise<User | null> {
const users = await this.getUsers({ filter: { email } });
return users[0] ?? null;
}
}