Merge branch 'develop' into upstream/2025.5.0

This commit is contained in:
dakkar 2025-06-10 14:02:32 +01:00
commit 3ebf9c4a71
317 changed files with 6144 additions and 2603 deletions

View file

@ -367,8 +367,10 @@ describe('AbuseReportNotificationService', () => {
id: idService.gen(),
targetUserId: alice.id,
targetUser: alice,
targetUserInstance: null,
reporterId: bob.id,
reporter: bob,
reporterInstance: null,
assigneeId: null,
assignee: null,
resolved: false,

View file

@ -11,6 +11,7 @@ import { GlobalModule } from '@/GlobalModule.js';
import { DI } from '@/di-symbols.js';
import { MetaService } from '@/core/MetaService.js';
import { CoreModule } from '@/core/CoreModule.js';
import { MetasRepository } from '@/models/_.js';
import type { TestingModule } from '@nestjs/testing';
import type { DataSource } from 'typeorm';
@ -39,8 +40,8 @@ describe('MetaService', () => {
});
test('fetch (cache)', async () => {
const db = app.get<DataSource>(DI.db);
const spy = jest.spyOn(db, 'transaction');
const metasRepository = app.get<MetasRepository>(DI.metasRepository);
const spy = jest.spyOn(metasRepository, 'createQueryBuilder');
const result = await metaService.fetch();
@ -49,12 +50,12 @@ describe('MetaService', () => {
});
test('fetch (force)', async () => {
const db = app.get<DataSource>(DI.db);
const spy = jest.spyOn(db, 'transaction');
const metasRepository = app.get<MetasRepository>(DI.metasRepository);
const spy = jest.spyOn(metasRepository, 'createQueryBuilder');
const result = await metaService.fetch(true);
expect(result.id).toBe('x');
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalled();
});
});

View file

@ -57,10 +57,13 @@ describe('NoteCreateService', () => {
channelId: null,
channel: null,
userHost: null,
userInstance: null,
replyUserId: null,
replyUserHost: null,
replyUserInstance: null,
renoteUserId: null,
renoteUserHost: null,
renoteUserInstance: null,
processErrors: [],
};

View file

@ -15,6 +15,7 @@ import type { MockFunctionMetadata } from 'jest-mock';
import { GlobalModule } from '@/GlobalModule.js';
import { RoleService } from '@/core/RoleService.js';
import {
InstancesRepository,
MiMeta,
MiRole,
MiRoleAssignment,
@ -39,6 +40,7 @@ const moduleMocker = new ModuleMocker(global);
describe('RoleService', () => {
let app: TestingModule;
let roleService: RoleService;
let instancesRepository: InstancesRepository;
let usersRepository: UsersRepository;
let rolesRepository: RolesRepository;
let roleAssignmentsRepository: RoleAssignmentsRepository;
@ -47,6 +49,19 @@ describe('RoleService', () => {
let clock: lolex.InstalledClock;
async function createUser(data: Partial<MiUser> = {}) {
if (data.host != null) {
await instancesRepository
.createQueryBuilder('instance')
.insert()
.values({
id: genAidx(Date.now()),
firstRetrievedAt: new Date(),
host: data.host,
})
.orIgnore()
.execute();
}
const un = secureRndstr(16);
const x = await usersRepository.insert({
id: genAidx(Date.now()),
@ -145,6 +160,7 @@ describe('RoleService', () => {
app.enableShutdownHooks();
roleService = app.get<RoleService>(RoleService);
instancesRepository = app.get<InstancesRepository>(DI.instancesRepository);
usersRepository = app.get<UsersRepository>(DI.usersRepository);
rolesRepository = app.get<RolesRepository>(DI.rolesRepository);
roleAssignmentsRepository = app.get<RoleAssignmentsRepository>(DI.roleAssignmentsRepository);

View file

@ -7,16 +7,18 @@ import { Test, TestingModule } from '@nestjs/testing';
import { describe, jest, test } from '@jest/globals';
import { In } from 'typeorm';
import { UserSearchService } from '@/core/UserSearchService.js';
import { FollowingsRepository, MiUser, UserProfilesRepository, UsersRepository } from '@/models/_.js';
import { FollowingsRepository, InstancesRepository, MiUser, UserProfilesRepository, UsersRepository } from '@/models/_.js';
import { IdService } from '@/core/IdService.js';
import { GlobalModule } from '@/GlobalModule.js';
import { DI } from '@/di-symbols.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { genAidx } from '@/misc/id/aidx.js';
describe('UserSearchService', () => {
let app: TestingModule;
let service: UserSearchService;
let instancesRepository: InstancesRepository;
let usersRepository: UsersRepository;
let followingsRepository: FollowingsRepository;
let idService: IdService;
@ -35,6 +37,19 @@ describe('UserSearchService', () => {
let bobby: MiUser;
async function createUser(data: Partial<MiUser> = {}) {
if (data.host != null) {
await instancesRepository
.createQueryBuilder('instance')
.insert()
.values({
id: genAidx(Date.now()),
firstRetrievedAt: new Date(),
host: data.host,
})
.orIgnore()
.execute();
}
const user = await usersRepository
.insert({
id: idService.gen(),
@ -104,6 +119,7 @@ describe('UserSearchService', () => {
await app.init();
instancesRepository = app.get<InstancesRepository>(DI.instancesRepository);
usersRepository = app.get(DI.usersRepository);
userProfilesRepository = app.get(DI.userProfilesRepository);
followingsRepository = app.get(DI.followingsRepository);

View file

@ -103,6 +103,25 @@ describe('ActivityPub', () => {
let config: Config;
const metaInitial = {
id: 'x',
name: 'Test Instance',
shortName: 'Test Instance',
description: 'Test Instance',
langs: [] as string[],
pinnedUsers: [] as string[],
hiddenTags: [] as string[],
prohibitedWordsForNameOfUser: [] as string[],
silencedHosts: [] as string[],
mediaSilencedHosts: [] as string[],
policies: {},
serverRules: [] as string[],
bannedEmailDomains: [] as string[],
preservedUsernames: [] as string[],
bubbleInstances: [] as string[],
trustedLinkUrlPatterns: [] as string[],
federation: 'all',
federationHosts: [] as string[],
allowUnsignedFetch: 'always',
cacheRemoteFiles: true,
cacheRemoteSensitiveFiles: true,
enableFanoutTimeline: true,

View file

@ -0,0 +1,91 @@
/*
* SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { diffArrays, diffArraysSimple } from '@/misc/diff-arrays.js';
describe(diffArrays, () => {
it('should return empty result when both inputs are null', () => {
const result = diffArrays(null, null);
expect(result.added).toHaveLength(0);
expect(result.removed).toHaveLength(0);
});
it('should return empty result when both inputs are empty', () => {
const result = diffArrays([], []);
expect(result.added).toHaveLength(0);
expect(result.removed).toHaveLength(0);
});
it('should remove before when after is empty', () => {
const result = diffArrays([1, 2, 3], []);
expect(result.added).toHaveLength(0);
expect(result.removed).toEqual([1, 2, 3]);
});
it('should deduplicate before when after is empty', () => {
const result = diffArrays([1, 1, 2, 2, 3], []);
expect(result.added).toHaveLength(0);
expect(result.removed).toEqual([1, 2, 3]);
});
it('should add after when before is empty', () => {
const result = diffArrays([], [1, 2, 3]);
expect(result.added).toEqual([1, 2, 3]);
expect(result.removed).toHaveLength(0);
});
it('should deduplicate after when before is empty', () => {
const result = diffArrays([], [1, 1, 2, 2, 3]);
expect(result.added).toEqual([1, 2, 3]);
expect(result.removed).toHaveLength(0);
});
it('should return diff when both have values', () => {
const result = diffArrays(
['a', 'b', 'c', 'd'],
['a', 'c', 'e', 'f'],
);
expect(result.added).toEqual(['e', 'f']);
expect(result.removed).toEqual(['b', 'd']);
});
});
describe(diffArraysSimple, () => {
it('should return false when both inputs are null', () => {
const result = diffArraysSimple(null, null);
expect(result).toBe(false);
});
it('should return false when both inputs are empty', () => {
const result = diffArraysSimple([], []);
expect(result).toBe(false);
});
it('should return true when before is populated and after is empty', () => {
const result = diffArraysSimple([1, 2, 3], []);
expect(result).toBe(true);
});
it('should return true when before is empty and after is populated', () => {
const result = diffArraysSimple([], [1, 2, 3]);
expect(result).toBe(true);
});
it('should return true when values have changed', () => {
const result = diffArraysSimple(
['a', 'a', 'b', 'c'],
['a', 'b', 'c', 'd'],
);
expect(result).toBe(true);
});
it('should return false when values have not changed', () => {
const result = diffArraysSimple(
['a', 'a', 'b', 'c'],
['a', 'b', 'c', 'c'],
);
expect(result).toBe(false);
});
});

View file

@ -40,10 +40,13 @@ const base: MiNote = {
channelId: null,
channel: null,
userHost: null,
userInstance: null,
replyUserId: null,
replyUserHost: null,
replyUserInstance: null,
renoteUserId: null,
renoteUserHost: null,
renoteUserInstance: null,
processErrors: [],
};

View file

@ -8,6 +8,9 @@ import { AbortError } from 'node-fetch';
import { isRetryableError } from '@/misc/is-retryable-error.js';
import { StatusError } from '@/misc/status-error.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import { CaptchaError, captchaErrorCodes } from '@/core/CaptchaService.js';
import { FastifyReplyError } from '@/misc/fastify-reply-error.js';
import { ConflictError } from '@/server/SkRateLimiterService.js';
describe(isRetryableError, () => {
it('should return true for retryable StatusError', () => {
@ -55,6 +58,78 @@ describe(isRetryableError, () => {
expect(result).toBeTruthy();
});
it('should return false for CaptchaError with verificationFailed', () => {
const error = new CaptchaError(captchaErrorCodes.verificationFailed, 'verificationFailed');
const result = isRetryableError(error);
expect(result).toBeFalsy();
});
it('should return false for CaptchaError with invalidProvider', () => {
const error = new CaptchaError(captchaErrorCodes.invalidProvider, 'invalidProvider');
const result = isRetryableError(error);
expect(result).toBeFalsy();
});
it('should return false for CaptchaError with invalidParameters', () => {
const error = new CaptchaError(captchaErrorCodes.invalidParameters, 'invalidParameters');
const result = isRetryableError(error);
expect(result).toBeFalsy();
});
it('should return true for CaptchaError with noResponseProvided', () => {
const error = new CaptchaError(captchaErrorCodes.noResponseProvided, 'noResponseProvided');
const result = isRetryableError(error);
expect(result).toBeTruthy();
});
it('should return true for CaptchaError with requestFailed', () => {
const error = new CaptchaError(captchaErrorCodes.requestFailed, 'requestFailed');
const result = isRetryableError(error);
expect(result).toBeTruthy();
});
it('should return true for CaptchaError with unknown', () => {
const error = new CaptchaError(captchaErrorCodes.unknown, 'unknown');
const result = isRetryableError(error);
expect(result).toBeTruthy();
});
it('should return true for CaptchaError with any other', () => {
const error = new CaptchaError(Symbol('temp'), 'unknown');
const result = isRetryableError(error);
expect(result).toBeTruthy();
});
it('should return false for FastifyReplyError', () => {
const error = new FastifyReplyError(400, 'test error');
const result = isRetryableError(error);
expect(result).toBeFalsy();
});
it('should return true for ConflictError', () => {
const error = new ConflictError('test error');
const result = isRetryableError(error);
expect(result).toBeTruthy();
});
it('should return true for AggregateError when all inners are retryable', () => {
const error = new AggregateError([
new ConflictError(),
new ConflictError(),
]);
const result = isRetryableError(error);
expect(result).toBeTruthy();
});
it('should return true for AggregateError when any error is not retryable', () => {
const error = new AggregateError([
new ConflictError(),
new StatusError('test err', 400),
]);
const result = isRetryableError(error);
expect(result).toBeFalsy();
});
const nonErrorInputs = [
[null, 'null'],
[undefined, 'undefined'],