first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
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
|
||||
const url = new URL(inviteAcceptUrl);
|
||||
const inviterId = url.searchParams.get('inviterId');
|
||||
const inviteeId = url.searchParams.get('inviteeId');
|
||||
|
||||
const acceptResponse = await this.api.request.post(`/rest/invitations/${inviteeId}/accept`, {
|
||||
data: {
|
||||
inviterId,
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user