mirror of
https://github.com/sim1222/misskey.git
synced 2025-08-04 07:26:29 +09:00
test(backend): APIテストの復活 (#10163)
* Revert 1c5291f818
* APIテストの復活
* apiテストの移行
* moduleNameMapper修正
* simpleGetでthrowしないように
status確認しているので要らない
* longer timeout
* ローカルでは問題ないのになんで
* case sensitive
* Run Nest instance within the current process
* Skip some setIntervals
* wait for 5 seconds
* kill them all!!
* logHeapUsage: true
* detectOpenHandlesがじゃましているらしい
* maxWorkers=1?
* restore drive api tests
* workerIdleMemoryLimit: 500MB
* 1024MiB
* Wait what
This commit is contained in:
committed by
GitHub
parent
53987fadd7
commit
61215e50ff
@ -1,4 +1,5 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { setTimeout } from 'node:timers/promises';
|
||||
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
||||
import type { MutingsRepository, NotificationsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js';
|
||||
import type { User } from '@/models/entities/User.js';
|
||||
import type { Notification } from '@/models/entities/Notification.js';
|
||||
@ -10,7 +11,9 @@ import { PushNotificationService } from '@/core/PushNotificationService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
@Injectable()
|
||||
export class CreateNotificationService {
|
||||
export class CreateNotificationService implements OnApplicationShutdown {
|
||||
#shutdownController = new AbortController();
|
||||
|
||||
constructor(
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
@ -40,11 +43,11 @@ export class CreateNotificationService {
|
||||
if (data.notifierId && (notifieeId === data.notifierId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
const profile = await this.userProfilesRepository.findOneBy({ userId: notifieeId });
|
||||
|
||||
|
||||
const isMuted = profile?.mutingNotificationTypes.includes(type);
|
||||
|
||||
|
||||
// Create notification
|
||||
const notification = await this.notificationsRepository.insert({
|
||||
id: this.idService.genId(),
|
||||
@ -56,18 +59,18 @@ export class CreateNotificationService {
|
||||
...data,
|
||||
} as Partial<Notification>)
|
||||
.then(x => this.notificationsRepository.findOneByOrFail(x.identifiers[0]));
|
||||
|
||||
|
||||
const packed = await this.notificationEntityService.pack(notification, {});
|
||||
|
||||
|
||||
// Publish notification event
|
||||
this.globalEventService.publishMainStream(notifieeId, 'notification', packed);
|
||||
|
||||
|
||||
// 2秒経っても(今回作成した)通知が既読にならなかったら「未読の通知がありますよ」イベントを発行する
|
||||
setTimeout(async () => {
|
||||
setTimeout(2000, 'unread note', { signal: this.#shutdownController.signal }).then(async () => {
|
||||
const fresh = await this.notificationsRepository.findOneBy({ id: notification.id });
|
||||
if (fresh == null) return; // 既に削除されているかもしれない
|
||||
if (fresh.isRead) return;
|
||||
|
||||
|
||||
//#region ただしミュートしているユーザーからの通知なら無視
|
||||
const mutings = await this.mutingsRepository.findBy({
|
||||
muterId: notifieeId,
|
||||
@ -76,14 +79,14 @@ export class CreateNotificationService {
|
||||
return;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
|
||||
this.globalEventService.publishMainStream(notifieeId, 'unreadNotification', packed);
|
||||
this.pushNotificationService.pushNotification(notifieeId, 'notification', packed);
|
||||
|
||||
|
||||
if (type === 'follow') this.emailNotificationFollow(notifieeId, await this.usersRepository.findOneByOrFail({ id: data.notifierId! }));
|
||||
if (type === 'receiveFollowRequest') this.emailNotificationReceiveFollowRequest(notifieeId, await this.usersRepository.findOneByOrFail({ id: data.notifierId! }));
|
||||
}, 2000);
|
||||
|
||||
}, () => { /* aborted, ignore it */ });
|
||||
|
||||
return notification;
|
||||
}
|
||||
|
||||
@ -103,7 +106,7 @@ export class CreateNotificationService {
|
||||
sendEmail(userProfile.email, i18n.t('_email._follow.title'), `${follower.name} (@${Acct.toString(follower)})`, `${follower.name} (@${Acct.toString(follower)})`);
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
@bindThis
|
||||
private async emailNotificationReceiveFollowRequest(userId: User['id'], follower: User) {
|
||||
/*
|
||||
@ -115,4 +118,8 @@ export class CreateNotificationService {
|
||||
sendEmail(userProfile.email, i18n.t('_email._receiveFollowRequest.title'), `${follower.name} (@${Acct.toString(follower)})`, `${follower.name} (@${Acct.toString(follower)})`);
|
||||
*/
|
||||
}
|
||||
|
||||
onApplicationShutdown(signal?: string | undefined): void {
|
||||
this.#shutdownController.abort();
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { setImmediate } from 'node:timers/promises';
|
||||
import * as mfm from 'mfm-js';
|
||||
import { In, DataSource } from 'typeorm';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
||||
import { extractMentions } from '@/misc/extract-mentions.js';
|
||||
import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
|
||||
import { extractHashtags } from '@/misc/extract-hashtags.js';
|
||||
@ -137,7 +138,9 @@ type Option = {
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class NoteCreateService {
|
||||
export class NoteCreateService implements OnApplicationShutdown {
|
||||
#shutdownController = new AbortController();
|
||||
|
||||
constructor(
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
@ -313,7 +316,10 @@ export class NoteCreateService {
|
||||
|
||||
const note = await this.insertNote(user, data, tags, emojis, mentionedUsers);
|
||||
|
||||
setImmediate(() => this.postNoteCreated(note, user, data, silent, tags!, mentionedUsers!));
|
||||
setImmediate('post created', { signal: this.#shutdownController.signal }).then(
|
||||
() => this.postNoteCreated(note, user, data, silent, tags!, mentionedUsers!),
|
||||
() => { /* aborted, ignore this */ },
|
||||
);
|
||||
|
||||
return note;
|
||||
}
|
||||
@ -756,4 +762,8 @@ export class NoteCreateService {
|
||||
|
||||
return mentionedUsers;
|
||||
}
|
||||
|
||||
onApplicationShutdown(signal?: string | undefined) {
|
||||
this.#shutdownController.abort();
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { setTimeout } from 'node:timers/promises';
|
||||
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
||||
import { In, IsNull, Not } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { User } from '@/models/entities/User.js';
|
||||
@ -15,7 +16,9 @@ import { AntennaService } from './AntennaService.js';
|
||||
import { PushNotificationService } from './PushNotificationService.js';
|
||||
|
||||
@Injectable()
|
||||
export class NoteReadService {
|
||||
export class NoteReadService implements OnApplicationShutdown {
|
||||
#shutdownController = new AbortController();
|
||||
|
||||
constructor(
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
@ -60,14 +63,14 @@ export class NoteReadService {
|
||||
});
|
||||
if (mute.map(m => m.muteeId).includes(note.userId)) return;
|
||||
//#endregion
|
||||
|
||||
|
||||
// スレッドミュート
|
||||
const threadMute = await this.noteThreadMutingsRepository.findOneBy({
|
||||
userId: userId,
|
||||
threadId: note.threadId ?? note.id,
|
||||
});
|
||||
if (threadMute) return;
|
||||
|
||||
|
||||
const unread = {
|
||||
id: this.idService.genId(),
|
||||
noteId: note.id,
|
||||
@ -77,15 +80,15 @@ export class NoteReadService {
|
||||
noteChannelId: note.channelId,
|
||||
noteUserId: note.userId,
|
||||
};
|
||||
|
||||
|
||||
await this.noteUnreadsRepository.insert(unread);
|
||||
|
||||
|
||||
// 2秒経っても既読にならなかったら「未読の投稿がありますよ」イベントを発行する
|
||||
setTimeout(async () => {
|
||||
setTimeout(2000, 'unread note', { signal: this.#shutdownController.signal }).then(async () => {
|
||||
const exist = await this.noteUnreadsRepository.findOneBy({ id: unread.id });
|
||||
|
||||
|
||||
if (exist == null) return;
|
||||
|
||||
|
||||
if (params.isMentioned) {
|
||||
this.globalEventService.publishMainStream(userId, 'unreadMention', note.id);
|
||||
}
|
||||
@ -95,8 +98,8 @@ export class NoteReadService {
|
||||
if (note.channelId) {
|
||||
this.globalEventService.publishMainStream(userId, 'unreadChannel', note.id);
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
}, () => { /* aborted, ignore it */ });
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async read(
|
||||
@ -113,24 +116,24 @@ export class NoteReadService {
|
||||
},
|
||||
select: ['followeeId'],
|
||||
})).map(x => x.followeeId));
|
||||
|
||||
|
||||
const myAntennas = (await this.antennaService.getAntennas()).filter(a => a.userId === userId);
|
||||
const readMentions: (Note | Packed<'Note'>)[] = [];
|
||||
const readSpecifiedNotes: (Note | Packed<'Note'>)[] = [];
|
||||
const readChannelNotes: (Note | Packed<'Note'>)[] = [];
|
||||
const readAntennaNotes: (Note | Packed<'Note'>)[] = [];
|
||||
|
||||
|
||||
for (const note of notes) {
|
||||
if (note.mentions && note.mentions.includes(userId)) {
|
||||
readMentions.push(note);
|
||||
} else if (note.visibleUserIds && note.visibleUserIds.includes(userId)) {
|
||||
readSpecifiedNotes.push(note);
|
||||
}
|
||||
|
||||
|
||||
if (note.channelId && followingChannels.has(note.channelId)) {
|
||||
readChannelNotes.push(note);
|
||||
}
|
||||
|
||||
|
||||
if (note.user != null) { // たぶんnullになることは無いはずだけど一応
|
||||
for (const antenna of myAntennas) {
|
||||
if (await this.antennaService.checkHitAntenna(antenna, note, note.user)) {
|
||||
@ -139,14 +142,14 @@ export class NoteReadService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ((readMentions.length > 0) || (readSpecifiedNotes.length > 0) || (readChannelNotes.length > 0)) {
|
||||
// Remove the record
|
||||
await this.noteUnreadsRepository.delete({
|
||||
userId: userId,
|
||||
noteId: In([...readMentions.map(n => n.id), ...readSpecifiedNotes.map(n => n.id), ...readChannelNotes.map(n => n.id)]),
|
||||
});
|
||||
|
||||
|
||||
// TODO: ↓まとめてクエリしたい
|
||||
|
||||
this.noteUnreadsRepository.countBy({
|
||||
@ -183,7 +186,7 @@ export class NoteReadService {
|
||||
noteId: In([...readMentions.map(n => n.id), ...readSpecifiedNotes.map(n => n.id)]),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (readAntennaNotes.length > 0) {
|
||||
await this.antennaNotesRepository.update({
|
||||
antennaId: In(myAntennas.map(a => a.id)),
|
||||
@ -191,14 +194,14 @@ export class NoteReadService {
|
||||
}, {
|
||||
read: true,
|
||||
});
|
||||
|
||||
|
||||
// TODO: まとめてクエリしたい
|
||||
for (const antenna of myAntennas) {
|
||||
const count = await this.antennaNotesRepository.countBy({
|
||||
antennaId: antenna.id,
|
||||
read: false,
|
||||
});
|
||||
|
||||
|
||||
if (count === 0) {
|
||||
this.globalEventService.publishMainStream(userId, 'readAntenna', antenna);
|
||||
this.pushNotificationService.pushNotification(userId, 'readAntenna', { antennaId: antenna.id });
|
||||
@ -213,4 +216,8 @@ export class NoteReadService {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onApplicationShutdown(signal?: string | undefined): void {
|
||||
this.#shutdownController.abort();
|
||||
}
|
||||
}
|
||||
|
@ -62,8 +62,10 @@ export class ChartManagementService implements OnApplicationShutdown {
|
||||
|
||||
async onApplicationShutdown(signal: string): Promise<void> {
|
||||
clearInterval(this.saveIntervalId);
|
||||
await Promise.all(
|
||||
this.charts.map(chart => chart.save()),
|
||||
);
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
await Promise.all(
|
||||
this.charts.map(chart => chart.save()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,8 +45,8 @@ export default class PerUserNotesChart extends Chart<typeof schema> {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async update(user: { id: User['id'] }, note: Note, isAdditional: boolean): Promise<void> {
|
||||
await this.commit({
|
||||
public update(user: { id: User['id'] }, note: Note, isAdditional: boolean): void {
|
||||
this.commit({
|
||||
'total': isAdditional ? 1 : -1,
|
||||
'inc': isAdditional ? 1 : 0,
|
||||
'dec': isAdditional ? 0 : 1,
|
||||
|
Reference in New Issue
Block a user