Merge remote-tracking branch 'misskey-dev/develop' into io
This commit is contained in:
commit
fd696a9621
113 changed files with 2272 additions and 443 deletions
|
@ -3,6 +3,8 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
||||
import { jest } from '@jest/globals';
|
||||
|
@ -20,6 +22,7 @@ import { IdService } from '@/core/IdService.js';
|
|||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
||||
import { NotificationService } from '@/core/NotificationService.js';
|
||||
import { RoleCondFormulaValue } from '@/models/Role.js';
|
||||
import { sleep } from '../utils.js';
|
||||
import type { TestingModule } from '@nestjs/testing';
|
||||
import type { MockFunctionMetadata } from 'jest-mock';
|
||||
|
@ -52,12 +55,26 @@ describe('RoleService', () => {
|
|||
id: genAidx(Date.now()),
|
||||
updatedAt: new Date(),
|
||||
lastUsedAt: new Date(),
|
||||
name: '',
|
||||
description: '',
|
||||
...data,
|
||||
})
|
||||
.then(x => rolesRepository.findOneByOrFail(x.identifiers[0]));
|
||||
}
|
||||
|
||||
function createConditionalRole(condFormula: RoleCondFormulaValue, data: Partial<MiRole> = {}) {
|
||||
return createRole({
|
||||
name: `[conditional] ${condFormula.type}`,
|
||||
target: 'conditional',
|
||||
condFormula: condFormula,
|
||||
...data,
|
||||
});
|
||||
}
|
||||
|
||||
function aidx() {
|
||||
return genAidx(Date.now());
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
clock = lolex.install({
|
||||
now: new Date(),
|
||||
|
@ -73,6 +90,7 @@ describe('RoleService', () => {
|
|||
CacheService,
|
||||
IdService,
|
||||
GlobalEventService,
|
||||
UserEntityService,
|
||||
{
|
||||
provide: NotificationService,
|
||||
useFactory: () => ({
|
||||
|
@ -209,79 +227,6 @@ describe('RoleService', () => {
|
|||
expect(result.driveCapacityMb).toBe(100);
|
||||
});
|
||||
|
||||
test('conditional role', async () => {
|
||||
const user1 = await createUser({
|
||||
id: genAidx(Date.now() - (1000 * 60 * 60 * 24 * 365)),
|
||||
});
|
||||
const user2 = await createUser({
|
||||
id: genAidx(Date.now() - (1000 * 60 * 60 * 24 * 365)),
|
||||
followersCount: 10,
|
||||
});
|
||||
await createRole({
|
||||
name: 'a',
|
||||
policies: {
|
||||
canManageCustomEmojis: {
|
||||
useDefault: false,
|
||||
priority: 0,
|
||||
value: true,
|
||||
},
|
||||
},
|
||||
target: 'conditional',
|
||||
condFormula: {
|
||||
id: '232a4221-9816-49a6-a967-ae0fac52ec5e',
|
||||
type: 'and',
|
||||
values: [{
|
||||
id: '2a37ef43-2d93-4c4d-87f6-f2fdb7d9b530',
|
||||
type: 'followersMoreThanOrEq',
|
||||
value: 10,
|
||||
}, {
|
||||
id: '1bd67839-b126-4f92-bad0-4e285dab453b',
|
||||
type: 'createdMoreThan',
|
||||
sec: 60 * 60 * 24 * 7,
|
||||
}],
|
||||
},
|
||||
});
|
||||
|
||||
metaService.fetch.mockResolvedValue({
|
||||
policies: {
|
||||
canManageCustomEmojis: false,
|
||||
},
|
||||
} as FIXME);
|
||||
|
||||
const user1Policies = await roleService.getUserPolicies(user1.id);
|
||||
const user2Policies = await roleService.getUserPolicies(user2.id);
|
||||
expect(user1Policies.canManageCustomEmojis).toBe(false);
|
||||
expect(user2Policies.canManageCustomEmojis).toBe(true);
|
||||
});
|
||||
|
||||
test('コンディショナルロール: マニュアルロールにアサイン済み', async () => {
|
||||
const [user1, user2, role1] = await Promise.all([
|
||||
createUser(),
|
||||
createUser(),
|
||||
createRole({
|
||||
name: 'manual role',
|
||||
}),
|
||||
]);
|
||||
const role2 = await createRole({
|
||||
name: 'conditional role',
|
||||
target: 'conditional',
|
||||
condFormula: {
|
||||
// idはバックエンドのロジックに必要ない?
|
||||
id: 'bdc612bd-9d54-4675-ae83-0499c82ea670',
|
||||
type: 'roleAssignedTo',
|
||||
roleId: role1.id,
|
||||
},
|
||||
});
|
||||
await roleService.assign(user2.id, role1.id);
|
||||
|
||||
const [u1role, u2role] = await Promise.all([
|
||||
roleService.getUserRoles(user1.id),
|
||||
roleService.getUserRoles(user2.id),
|
||||
]);
|
||||
expect(u1role.some(r => r.id === role2.id)).toBe(false);
|
||||
expect(u2role.some(r => r.id === role2.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('expired role', async () => {
|
||||
const user = await createUser();
|
||||
const role = await createRole({
|
||||
|
@ -320,6 +265,427 @@ describe('RoleService', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('conditional role', () => {
|
||||
test('~かつ~', async () => {
|
||||
const [user1, user2, user3, user4] = await Promise.all([
|
||||
createUser({ isBot: true, isCat: false, isSuspended: false }),
|
||||
createUser({ isBot: false, isCat: true, isSuspended: false }),
|
||||
createUser({ isBot: true, isCat: true, isSuspended: false }),
|
||||
createUser({ isBot: false, isCat: false, isSuspended: true }),
|
||||
]);
|
||||
const role1 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isBot',
|
||||
});
|
||||
const role2 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isCat',
|
||||
});
|
||||
const role3 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isSuspended',
|
||||
});
|
||||
const role4 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'and',
|
||||
values: [role1.condFormula, role2.condFormula],
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
const actual3 = await roleService.getUserRoles(user3.id);
|
||||
const actual4 = await roleService.getUserRoles(user4.id);
|
||||
expect(actual1.some(r => r.id === role4.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role4.id)).toBe(false);
|
||||
expect(actual3.some(r => r.id === role4.id)).toBe(true);
|
||||
expect(actual4.some(r => r.id === role4.id)).toBe(false);
|
||||
});
|
||||
|
||||
test('~または~', async () => {
|
||||
const [user1, user2, user3, user4] = await Promise.all([
|
||||
createUser({ isBot: true, isCat: false, isSuspended: false }),
|
||||
createUser({ isBot: false, isCat: true, isSuspended: false }),
|
||||
createUser({ isBot: true, isCat: true, isSuspended: false }),
|
||||
createUser({ isBot: false, isCat: false, isSuspended: true }),
|
||||
]);
|
||||
const role1 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isBot',
|
||||
});
|
||||
const role2 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isCat',
|
||||
});
|
||||
const role3 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isSuspended',
|
||||
});
|
||||
const role4 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'or',
|
||||
values: [role1.condFormula, role2.condFormula],
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
const actual3 = await roleService.getUserRoles(user3.id);
|
||||
const actual4 = await roleService.getUserRoles(user4.id);
|
||||
expect(actual1.some(r => r.id === role4.id)).toBe(true);
|
||||
expect(actual2.some(r => r.id === role4.id)).toBe(true);
|
||||
expect(actual3.some(r => r.id === role4.id)).toBe(true);
|
||||
expect(actual4.some(r => r.id === role4.id)).toBe(false);
|
||||
});
|
||||
|
||||
test('~ではない', async () => {
|
||||
const [user1, user2, user3] = await Promise.all([
|
||||
createUser({ isBot: true, isCat: false, isSuspended: false }),
|
||||
createUser({ isBot: false, isCat: true, isSuspended: false }),
|
||||
createUser({ isBot: true, isCat: true, isSuspended: false }),
|
||||
]);
|
||||
const role1 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isBot',
|
||||
});
|
||||
const role2 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isCat',
|
||||
});
|
||||
const role4 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'not',
|
||||
value: role1.condFormula,
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
const actual3 = await roleService.getUserRoles(user3.id);
|
||||
expect(actual1.some(r => r.id === role4.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role4.id)).toBe(true);
|
||||
expect(actual3.some(r => r.id === role4.id)).toBe(false);
|
||||
});
|
||||
|
||||
test('マニュアルロールにアサイン済み', async () => {
|
||||
const [user1, user2, role1] = await Promise.all([
|
||||
createUser(),
|
||||
createUser(),
|
||||
createRole({
|
||||
name: 'manual role',
|
||||
}),
|
||||
]);
|
||||
const role2 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'roleAssignedTo',
|
||||
roleId: role1.id,
|
||||
});
|
||||
await roleService.assign(user2.id, role1.id);
|
||||
|
||||
const [u1role, u2role] = await Promise.all([
|
||||
roleService.getUserRoles(user1.id),
|
||||
roleService.getUserRoles(user2.id),
|
||||
]);
|
||||
expect(u1role.some(r => r.id === role2.id)).toBe(false);
|
||||
expect(u2role.some(r => r.id === role2.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('ローカルユーザのみ', async () => {
|
||||
const [user1, user2] = await Promise.all([
|
||||
createUser({ host: null }),
|
||||
createUser({ host: 'example.com' }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isLocal',
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(true);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(false);
|
||||
});
|
||||
|
||||
test('リモートユーザのみ', async () => {
|
||||
const [user1, user2] = await Promise.all([
|
||||
createUser({ host: null }),
|
||||
createUser({ host: 'example.com' }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isRemote',
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('サスペンド済みユーザである', async () => {
|
||||
const [user1, user2] = await Promise.all([
|
||||
createUser({ isSuspended: false }),
|
||||
createUser({ isSuspended: true }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isSuspended',
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('鍵アカウントユーザである', async () => {
|
||||
const [user1, user2] = await Promise.all([
|
||||
createUser({ isLocked: false }),
|
||||
createUser({ isLocked: true }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isLocked',
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('botユーザである', async () => {
|
||||
const [user1, user2] = await Promise.all([
|
||||
createUser({ isBot: false }),
|
||||
createUser({ isBot: true }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isBot',
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('猫である', async () => {
|
||||
const [user1, user2] = await Promise.all([
|
||||
createUser({ isCat: false }),
|
||||
createUser({ isCat: true }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isCat',
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('「ユーザを見つけやすくする」が有効なアカウント', async () => {
|
||||
const [user1, user2] = await Promise.all([
|
||||
createUser({ isExplorable: false }),
|
||||
createUser({ isExplorable: true }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isExplorable',
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('ユーザが作成されてから指定期間経過した', async () => {
|
||||
const base = new Date();
|
||||
base.setMinutes(base.getMinutes() - 5);
|
||||
|
||||
const d1 = new Date(base);
|
||||
const d2 = new Date(base);
|
||||
const d3 = new Date(base);
|
||||
d1.setSeconds(d1.getSeconds() - 1);
|
||||
d3.setSeconds(d3.getSeconds() + 1);
|
||||
|
||||
const [user1, user2, user3] = await Promise.all([
|
||||
// 4:59
|
||||
createUser({ id: genAidx(d1.getTime()) }),
|
||||
// 5:00
|
||||
createUser({ id: genAidx(d2.getTime()) }),
|
||||
// 5:01
|
||||
createUser({ id: genAidx(d3.getTime()) }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'createdLessThan',
|
||||
// 5 minutes
|
||||
sec: 300,
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
const actual3 = await roleService.getUserRoles(user3.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual3.some(r => r.id === role.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('ユーザが作成されてから指定期間経っていない', async () => {
|
||||
const base = new Date();
|
||||
base.setMinutes(base.getMinutes() - 5);
|
||||
|
||||
const d1 = new Date(base);
|
||||
const d2 = new Date(base);
|
||||
const d3 = new Date(base);
|
||||
d1.setSeconds(d1.getSeconds() - 1);
|
||||
d3.setSeconds(d3.getSeconds() + 1);
|
||||
|
||||
const [user1, user2, user3] = await Promise.all([
|
||||
// 4:59
|
||||
createUser({ id: genAidx(d1.getTime()) }),
|
||||
// 5:00
|
||||
createUser({ id: genAidx(d2.getTime()) }),
|
||||
// 5:01
|
||||
createUser({ id: genAidx(d3.getTime()) }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'createdMoreThan',
|
||||
// 5 minutes
|
||||
sec: 300,
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
const actual3 = await roleService.getUserRoles(user3.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(true);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual3.some(r => r.id === role.id)).toBe(false);
|
||||
});
|
||||
|
||||
test('フォロワー数が指定値以下', async () => {
|
||||
const [user1, user2, user3] = await Promise.all([
|
||||
createUser({ followersCount: 99 }),
|
||||
createUser({ followersCount: 100 }),
|
||||
createUser({ followersCount: 101 }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'followersLessThanOrEq',
|
||||
value: 100,
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
const actual3 = await roleService.getUserRoles(user3.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(true);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
expect(actual3.some(r => r.id === role.id)).toBe(false);
|
||||
});
|
||||
|
||||
test('フォロワー数が指定値以下', async () => {
|
||||
const [user1, user2, user3] = await Promise.all([
|
||||
createUser({ followersCount: 99 }),
|
||||
createUser({ followersCount: 100 }),
|
||||
createUser({ followersCount: 101 }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'followersMoreThanOrEq',
|
||||
value: 100,
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
const actual3 = await roleService.getUserRoles(user3.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
expect(actual3.some(r => r.id === role.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('フォロー数が指定値以下', async () => {
|
||||
const [user1, user2, user3] = await Promise.all([
|
||||
createUser({ followingCount: 99 }),
|
||||
createUser({ followingCount: 100 }),
|
||||
createUser({ followingCount: 101 }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'followingLessThanOrEq',
|
||||
value: 100,
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
const actual3 = await roleService.getUserRoles(user3.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(true);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
expect(actual3.some(r => r.id === role.id)).toBe(false);
|
||||
});
|
||||
|
||||
test('フォロー数が指定値以上', async () => {
|
||||
const [user1, user2, user3] = await Promise.all([
|
||||
createUser({ followingCount: 99 }),
|
||||
createUser({ followingCount: 100 }),
|
||||
createUser({ followingCount: 101 }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'followingMoreThanOrEq',
|
||||
value: 100,
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
const actual3 = await roleService.getUserRoles(user3.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
expect(actual3.some(r => r.id === role.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('ノート数が指定値以下', async () => {
|
||||
const [user1, user2, user3] = await Promise.all([
|
||||
createUser({ notesCount: 9 }),
|
||||
createUser({ notesCount: 10 }),
|
||||
createUser({ notesCount: 11 }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'notesLessThanOrEq',
|
||||
value: 10,
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
const actual3 = await roleService.getUserRoles(user3.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(true);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
expect(actual3.some(r => r.id === role.id)).toBe(false);
|
||||
});
|
||||
|
||||
test('ノート数が指定値以上', async () => {
|
||||
const [user1, user2, user3] = await Promise.all([
|
||||
createUser({ notesCount: 9 }),
|
||||
createUser({ notesCount: 10 }),
|
||||
createUser({ notesCount: 11 }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'notesMoreThanOrEq',
|
||||
value: 10,
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
const actual3 = await roleService.getUserRoles(user3.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
expect(actual3.some(r => r.id === role.id)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('assign', () => {
|
||||
test('公開ロールの場合は通知される', async () => {
|
||||
const user = await createUser();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue