summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortamaina <tamaina@hotmail.co.jp>2025-07-30 21:41:46 +0900
committerGitHub <noreply@github.com>2025-07-30 21:41:46 +0900
commit8c65d8d0202c5abce3b2104b5b0f24869dd6e54c (patch)
tree728faf0751a7a0560d223fe4deeed3107ee83b91
parentfix(frontend): inline な SearchMarker のパスが正しくない問題を... (diff)
downloadmisskey-8c65d8d0202c5abce3b2104b5b0f24869dd6e54c.tar.gz
misskey-8c65d8d0202c5abce3b2104b5b0f24869dd6e54c.tar.bz2
misskey-8c65d8d0202c5abce3b2104b5b0f24869dd6e54c.zip
test(backend): e2e/timelines.ts: 非FTT時のテストを追加, 凍結のテストを追加, これにかかる幾つかのバグ修正 (#16284)
* test(backend): 非FTT時のテストを追加 * clean up * skip test about reply * Fix #16289 * clean up * cherry pick * add renote test * Fix https://github.com/misskey-dev/misskey/issues/16293 * remove debug log
-rw-r--r--packages/backend/src/core/FanoutTimelineEndpointService.ts16
-rw-r--r--packages/backend/src/server/api/endpoints/notes/timeline.ts8
-rw-r--r--packages/backend/test/e2e/timelines.ts2478
3 files changed, 1441 insertions, 1061 deletions
diff --git a/packages/backend/src/core/FanoutTimelineEndpointService.ts b/packages/backend/src/core/FanoutTimelineEndpointService.ts
index 97b617096a..94c5691bf4 100644
--- a/packages/backend/src/core/FanoutTimelineEndpointService.ts
+++ b/packages/backend/src/core/FanoutTimelineEndpointService.ts
@@ -20,6 +20,8 @@ import { CacheService } from '@/core/CacheService.js';
import { isReply } from '@/misc/is-reply.js';
import { isInstanceMuted } from '@/misc/is-instance-muted.js';
+type NoteFilter = (note: MiNote) => boolean;
+
type TimelineOptions = {
untilId: string | null,
sinceId: string | null,
@@ -28,7 +30,7 @@ type TimelineOptions = {
me?: { id: MiUser['id'] } | undefined | null,
useDbFallback: boolean,
redisTimelines: FanoutTimelineName[],
- noteFilter?: (note: MiNote) => boolean,
+ noteFilter?: NoteFilter,
alwaysIncludeMyNotes?: boolean;
ignoreAuthorFromBlock?: boolean;
ignoreAuthorFromMute?: boolean;
@@ -79,7 +81,7 @@ export class FanoutTimelineEndpointService {
const shouldFallbackToDb = noteIds.length === 0 || ps.sinceId != null && ps.sinceId < oldestNoteId;
if (!shouldFallbackToDb) {
- let filter = ps.noteFilter ?? (_note => true);
+ let filter = ps.noteFilter ?? (_note => true) as NoteFilter;
if (ps.alwaysIncludeMyNotes && ps.me) {
const me = ps.me;
@@ -145,15 +147,11 @@ export class FanoutTimelineEndpointService {
{
const parentFilter = filter;
filter = (note) => {
- const noteJoined = note as MiNote & {
- renoteUser: MiUser | null;
- replyUser: MiUser | null;
- };
if (!ps.ignoreAuthorFromUserSuspension) {
if (note.user!.isSuspended) return false;
}
- if (note.userId !== note.renoteUserId && noteJoined.renoteUser?.isSuspended) return false;
- if (note.userId !== note.replyUserId && noteJoined.replyUser?.isSuspended) return false;
+ if (note.userId !== note.renoteUserId && note.renote?.user?.isSuspended) return false;
+ if (note.userId !== note.replyUserId && note.reply?.user?.isSuspended) return false;
return parentFilter(note);
};
@@ -200,7 +198,7 @@ export class FanoutTimelineEndpointService {
return await ps.dbFallback(ps.untilId, ps.sinceId, ps.limit);
}
- private async getAndFilterFromDb(noteIds: string[], noteFilter: (note: MiNote) => boolean, idCompare: (a: string, b: string) => number): Promise<MiNote[]> {
+ private async getAndFilterFromDb(noteIds: string[], noteFilter: NoteFilter, idCompare: (a: string, b: string) => number): Promise<MiNote[]> {
const query = this.notesRepository.createQueryBuilder('note')
.where('note.id IN (:...noteIds)', { noteIds: noteIds })
.innerJoinAndSelect('note.user', 'user')
diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts
index c76cca1518..1f3631ae3d 100644
--- a/packages/backend/src/server/api/endpoints/notes/timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts
@@ -237,7 +237,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
if (ps.withRenotes === false) {
- query.andWhere('note.renoteId IS NULL');
+ query.andWhere(new Brackets(qb => {
+ qb.orWhere('note.renoteId IS NULL');
+ qb.orWhere(new Brackets(qb => {
+ qb.orWhere('note.text IS NOT NULL');
+ qb.orWhere('note.fileIds != \'{}\'');
+ }));
+ }));
}
//#endregion
diff --git a/packages/backend/test/e2e/timelines.ts b/packages/backend/test/e2e/timelines.ts
index e53c3d8f34..106b2857b5 100644
--- a/packages/backend/test/e2e/timelines.ts
+++ b/packages/backend/test/e2e/timelines.ts
@@ -9,6 +9,7 @@
import * as assert from 'assert';
import { setTimeout } from 'node:timers/promises';
import { Redis } from 'ioredis';
+import { SignupResponse, Note, UserList } from 'misskey-js/entities.js';
import { api, post, randomString, sendEnvUpdateRequest, signup, uploadUrl } from '../utils.js';
import { loadConfig } from '@/config.js';
@@ -16,1554 +17,1929 @@ function genHost() {
return randomString() + '.example.com';
}
-function waitForPushToTl() {
- return setTimeout(500);
-}
-
let redisForTimelines: Redis;
+let root: SignupResponse;
describe('Timelines', () => {
- beforeAll(() => {
+ beforeAll(async () => {
redisForTimelines = new Redis(loadConfig().redisForTimelines);
- });
+ root = await signup({ username: 'root' });
+ }, 1000 * 60 * 2);
- describe('Home TL', () => {
- test.concurrent('自分の visibility: followers なノートが含まれる', async () => {
- const [alice] = await Promise.all([signup()]);
+ describe.each([
+ { enableFanoutTimeline: true },
+ { enableFanoutTimeline: false },
+ ])('Timelines (enableFanoutTimeline: $enableFanoutTimeline)', ({ enableFanoutTimeline }) => {
+ function waitForPushToTl() {
+ return setTimeout(250);
+ }
- const aliceNote = await post(alice, { text: 'hi', visibility: 'followers' });
+ beforeAll(async () => {
+ await api('admin/update-meta', { enableFanoutTimeline }, root);
+ }, 1000 * 60 * 2);
- await waitForPushToTl();
+ describe('Home TL', () => {
+ test('自分の visibility: followers なノートが含まれる', async () => {
+ const [alice] = await Promise.all([signup()]);
- const res = await api('notes/timeline', { limit: 100 }, alice);
+ const aliceNote = await post(alice, { text: 'hi', visibility: 'followers' });
- assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
- assert.strictEqual(res.body.find(note => note.id === aliceNote.id)?.text, 'hi');
- });
+ await waitForPushToTl();
- test.concurrent('フォローしているユーザーのノートが含まれる', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ const res = await api('notes/timeline', { limit: 100 }, alice);
- await api('following/create', { userId: bob.id }, alice);
- await setTimeout(1000);
- const bobNote = await post(bob, { text: 'hi' });
- const carolNote = await post(carol, { text: 'hi' });
+ assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
+ assert.strictEqual(res.body.find(note => note.id === aliceNote.id)?.text, 'hi');
+ });
- await waitForPushToTl();
+ test('フォローしているユーザーのノートが含まれる', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- const res = await api('notes/timeline', { limit: 100 }, alice);
+ await api('following/create', { userId: bob.id }, alice);
+ await setTimeout(250);
+ const bobNote = await post(bob, { text: 'hi' });
+ const carolNote = await post(carol, { text: 'hi' });
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
- });
+ await waitForPushToTl();
- test.concurrent('フォローしているユーザーの visibility: followers なノートが含まれる', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ const res = await api('notes/timeline', { limit: 100 }, alice);
- await api('following/create', { userId: bob.id }, alice);
- await setTimeout(1000);
- const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
- const carolNote = await post(carol, { text: 'hi' });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
+ });
- await waitForPushToTl();
+ test('フォローしているユーザーの visibility: followers なノートが含まれる', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- const res = await api('notes/timeline', { limit: 100 }, alice);
+ await api('following/create', { userId: bob.id }, alice);
+ await setTimeout(250);
+ const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
+ const carolNote = await post(carol, { text: 'hi' });
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- assert.strictEqual(res.body.find(note => note.id === bobNote.id)?.text, 'hi');
- assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
- });
+ await waitForPushToTl();
- test.concurrent('withReplies: false でフォローしているユーザーの他人への返信が含まれない', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ const res = await api('notes/timeline', { limit: 100 }, alice);
- await api('following/create', { userId: bob.id }, alice);
- await setTimeout(1000);
- const carolNote = await post(carol, { text: 'hi' });
- const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ assert.strictEqual(res.body.find(note => note.id === bobNote.id)?.text, 'hi');
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
+ });
- await waitForPushToTl();
+ test('withReplies: false でフォローしているユーザーの他人への返信が含まれない', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- const res = await api('notes/timeline', { limit: 100 }, alice);
+ await api('following/create', { userId: bob.id }, alice);
+ await setTimeout(250);
+ const carolNote = await post(carol, { text: 'hi' });
+ const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
- });
+ await waitForPushToTl();
- test.concurrent('withReplies: true でフォローしているユーザーの他人への返信が含まれる', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ const res = await api('notes/timeline', { limit: 100 }, alice);
- await api('following/create', { userId: bob.id }, alice);
- await api('following/update', { userId: bob.id, withReplies: true }, alice);
- await setTimeout(1000);
- const carolNote = await post(carol, { text: 'hi' });
- const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
+ });
- await waitForPushToTl();
+ test('withReplies: true でフォローしているユーザーの他人への返信が含まれる', async () => {
+ /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ if (!enableFanoutTimeline) return;
- const res = await api('notes/timeline', { limit: 100 }, alice);
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
- });
+ await api('following/create', { userId: bob.id }, alice);
+ await api('following/update', { userId: bob.id, withReplies: true }, alice);
+ await setTimeout(250);
+ const carolNote = await post(carol, { text: 'hi' });
+ const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
- test.concurrent('withReplies: true でフォローしているユーザーの他人へのDM返信が含まれない', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ await waitForPushToTl();
- await api('following/create', { userId: bob.id }, alice);
- await api('following/update', { userId: bob.id, withReplies: true }, alice);
- await setTimeout(1000);
- const carolNote = await post(carol, { text: 'hi' });
- const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id, visibility: 'specified', visibleUserIds: [carolNote.id] });
+ const res = await api('notes/timeline', { limit: 100 }, alice);
- await waitForPushToTl();
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
+ });
- const res = await api('notes/timeline', { limit: 100 }, alice);
+ test('withReplies: true でフォローしているユーザーの他人へのDM返信が含まれない', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
- });
+ await api('following/create', { userId: bob.id }, alice);
+ await api('following/update', { userId: bob.id, withReplies: true }, alice);
+ await setTimeout(250);
+ const carolNote = await post(carol, { text: 'hi' });
+ const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id, visibility: 'specified', visibleUserIds: [carolNote.id] });
- test.concurrent('withReplies: true でフォローしているユーザーの他人の visibility: followers な投稿への返信が含まれない', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ await waitForPushToTl();
- await api('following/create', { userId: carol.id }, bob);
- await api('following/create', { userId: bob.id }, alice);
- await api('following/update', { userId: bob.id, withReplies: true }, alice);
- await setTimeout(1000);
- const carolNote = await post(carol, { text: 'hi', visibility: 'followers' });
- const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
+ const res = await api('notes/timeline', { limit: 100 }, alice);
- await waitForPushToTl();
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
+ });
- const res = await api('notes/timeline', { limit: 100 }, alice);
+ test('withReplies: true でフォローしているユーザーの他人の visibility: followers な投稿への返信が含まれない', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
- });
+ await api('following/create', { userId: carol.id }, bob);
+ await api('following/create', { userId: bob.id }, alice);
+ await api('following/update', { userId: bob.id, withReplies: true }, alice);
+ await setTimeout(250);
+ const carolNote = await post(carol, { text: 'hi', visibility: 'followers' });
+ const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
- test.concurrent('withReplies: true でフォローしているユーザーの行った別のフォローしているユーザーの visibility: followers な投稿への返信が含まれる', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ await waitForPushToTl();
- await api('following/create', { userId: bob.id }, alice);
- await api('following/create', { userId: carol.id }, alice);
- await api('following/create', { userId: carol.id }, bob);
- await api('following/update', { userId: bob.id, withReplies: true }, alice);
- await setTimeout(1000);
- const carolNote = await post(carol, { text: 'hi', visibility: 'followers' });
- const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
+ const res = await api('notes/timeline', { limit: 100 }, alice);
- await waitForPushToTl();
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
+ });
- const res = await api('notes/timeline', { limit: 100 }, alice);
+ test('withReplies: true でフォローしているユーザーの行った別のフォローしているユーザーの visibility: followers な投稿への返信が含まれる', async () => {
+ /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ if (!enableFanoutTimeline) return;
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- assert.strictEqual(res.body.some(note => note.id === carolNote.id), true);
- assert.strictEqual(res.body.find(note => note.id === carolNote.id)?.text, 'hi');
- });
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- test.concurrent('withReplies: true でフォローしているユーザーの自分の visibility: followers な投稿への返信が含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ await api('following/create', { userId: bob.id }, alice);
+ await api('following/create', { userId: carol.id }, alice);
+ await api('following/create', { userId: carol.id }, bob);
+ await api('following/update', { userId: bob.id, withReplies: true }, alice);
+ await setTimeout(250);
+ const carolNote = await post(carol, { text: 'hi', visibility: 'followers' });
+ const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
- await api('following/create', { userId: bob.id }, alice);
- await api('following/create', { userId: alice.id }, bob);
- await api('following/update', { userId: bob.id, withReplies: true }, alice);
- await setTimeout(1000);
- const aliceNote = await post(alice, { text: 'hi', visibility: 'followers' });
- const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id });
+ await waitForPushToTl();
- await waitForPushToTl();
+ const res = await api('notes/timeline', { limit: 100 }, alice);
- const res = await api('notes/timeline', { limit: 100 }, alice);
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), true);
+ assert.strictEqual(res.body.find(note => note.id === carolNote.id)?.text, 'hi');
+ });
- assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
- assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true);
- });
+ test('withReplies: true でフォローしているユーザーの自分の visibility: followers な投稿への返信が含まれる', async () => {
+ /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ if (!enableFanoutTimeline) return;
- test.concurrent('withReplies: true でフォローしているユーザーの行った別のフォローしているユーザーの投稿への visibility: specified な返信が含まれない', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ const [alice, bob] = await Promise.all([signup(), signup()]);
- await api('following/create', { userId: bob.id }, alice);
- await api('following/create', { userId: carol.id }, alice);
- await api('following/update', { userId: bob.id, withReplies: true }, alice);
- await setTimeout(1000);
- const carolNote = await post(carol, { text: 'hi' });
- const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id, visibility: 'specified', visibleUserIds: [carolNote.id] });
+ await api('following/create', { userId: bob.id }, alice);
+ await api('following/create', { userId: alice.id }, bob);
+ await api('following/update', { userId: bob.id, withReplies: true }, alice);
+ await setTimeout(250);
+ const aliceNote = await post(alice, { text: 'hi', visibility: 'followers' });
+ const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('notes/timeline', { limit: 100 }, alice);
+ const res = await api('notes/timeline', { limit: 100 }, alice);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- assert.strictEqual(res.body.some(note => note.id === carolNote.id), true);
- });
+ assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
+ assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true);
+ });
- test.concurrent('withReplies: false でフォローしているユーザーのそのユーザー自身への返信が含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ test('withReplies: true でフォローしているユーザーの行った別のフォローしているユーザーの投稿への visibility: specified な返信が含まれない', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- await api('following/create', { userId: bob.id }, alice);
- await setTimeout(1000);
- const bobNote1 = await post(bob, { text: 'hi' });
- const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id });
+ await api('following/create', { userId: bob.id }, alice);
+ await api('following/create', { userId: carol.id }, alice);
+ await api('following/update', { userId: bob.id, withReplies: true }, alice);
+ await setTimeout(250);
+ const carolNote = await post(carol, { text: 'hi' });
+ const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id, visibility: 'specified', visibleUserIds: [carolNote.id] });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('notes/timeline', { limit: 100 }, alice);
+ const res = await api('notes/timeline', { limit: 100 }, alice);
- assert.strictEqual(res.body.some(note => note.id === bobNote1.id), true);
- assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true);
- });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), true);
+ });
- test.concurrent('withReplies: false でフォローしているユーザーからの自分への返信が含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ test('withReplies: false でフォローしているユーザーのそのユーザー自身への返信が含まれる', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup()]);
- await api('following/create', { userId: bob.id }, alice);
- await setTimeout(1000);
- const aliceNote = await post(alice, { text: 'hi' });
- const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id });
+ await api('following/create', { userId: bob.id }, alice);
+ await setTimeout(250);
+ const bobNote1 = await post(bob, { text: 'hi' });
+ const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('notes/timeline', { limit: 100 }, alice);
+ const res = await api('notes/timeline', { limit: 100 }, alice);
- assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- });
+ assert.strictEqual(res.body.some(note => note.id === bobNote1.id), true);
+ assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true);
+ });
- test.concurrent('自分の他人への返信が含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ test('withReplies: false でフォローしているユーザーからの自分への返信が含まれる', async () => {
+ /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ if (!enableFanoutTimeline) return;
- const bobNote = await post(bob, { text: 'hi' });
- const aliceNote = await post(alice, { text: 'hi', replyId: bobNote.id });
+ const [alice, bob] = await Promise.all([signup(), signup()]);
- await waitForPushToTl();
+ await api('following/create', { userId: bob.id }, alice);
+ await setTimeout(250);
+ const aliceNote = await post(alice, { text: 'hi' });
+ const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id });
- const res = await api('notes/timeline', { limit: 100 }, alice);
+ await waitForPushToTl();
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
- });
+ const res = await api('notes/timeline', { limit: 100 }, alice);
- test.concurrent('フォローしているユーザーの他人の投稿のリノートが含まれる', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ });
- await api('following/create', { userId: bob.id }, alice);
- await setTimeout(1000);
- const carolNote = await post(carol, { text: 'hi' });
- const bobNote = await post(bob, { renoteId: carolNote.id });
+ test('自分の他人への返信が含まれる', async () => {
+ /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ if (!enableFanoutTimeline) return;
- await waitForPushToTl();
+ const [alice, bob] = await Promise.all([signup(), signup()]);
- const res = await api('notes/timeline', { limit: 100 }, alice);
+ const bobNote = await post(bob, { text: 'hi' });
+ const aliceNote = await post(alice, { text: 'hi', replyId: bobNote.id });
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
- });
+ await waitForPushToTl();
- test.concurrent('[withRenotes: false] フォローしているユーザーの他人の投稿のリノートが含まれない', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ const res = await api('notes/timeline', { limit: 100 }, alice);
- await api('following/create', { userId: bob.id }, alice);
- await setTimeout(1000);
- const carolNote = await post(carol, { text: 'hi' });
- const bobNote = await post(bob, { renoteId: carolNote.id });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
+ });
- await waitForPushToTl();
+ test('フォローしているユーザーの他人の投稿のリノートが含まれる', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- const res = await api('notes/timeline', {
- withRenotes: false,
- }, alice);
+ await api('following/create', { userId: bob.id }, alice);
+ await setTimeout(250);
+ const carolNote = await post(carol, { text: 'hi' });
+ const bobNote = await post(bob, { renoteId: carolNote.id });
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
- });
+ await waitForPushToTl();
- test.concurrent('[withRenotes: false] フォローしているユーザーの他人の投稿の引用が含まれる', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ const res = await api('notes/timeline', { limit: 100 }, alice);
- await api('following/create', { userId: bob.id }, alice);
- await setTimeout(1000);
- const carolNote = await post(carol, { text: 'hi' });
- const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
+ });
- await waitForPushToTl();
+ test('[withRenotes: false] フォローしているユーザーの投稿が含まれる', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- const res = await api('notes/timeline', {
- withRenotes: false,
- }, alice);
+ await api('following/create', { userId: bob.id }, alice);
+ await setTimeout(250);
+ const bobNote = await post(bob, { text: 'hi' });
+ const carolNote = await post(carol, { text: 'hi' });
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
- });
+ await waitForPushToTl();
- test.concurrent('フォローしているユーザーの他人への visibility: specified なノートが含まれない', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ const res = await api('notes/timeline', {
+ limit: 100,
+ withRenotes: false,
+ }, alice);
- await api('following/create', { userId: bob.id }, alice);
- await setTimeout(1000);
- const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [carol.id] });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
+ });
- await waitForPushToTl();
+ test('[withRenotes: false] フォローしているユーザーのファイルのみの投稿が含まれる', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- const res = await api('notes/timeline', { limit: 100 }, alice);
+ await api('following/create', { userId: bob.id }, alice);
+ await setTimeout(250);
+ const [bobFile, carolFile] = await Promise.all([
+ uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png'),
+ uploadUrl(carol, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png'),
+ ]);
+ const bobNote = await post(bob, { fileIds: [bobFile.id] });
+ const carolNote = await post(carol, { fileIds: [carolFile.id] });
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- });
+ await waitForPushToTl();
- test.concurrent('フォローしているユーザーが行ったミュートしているユーザーのリノートが含まれない', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ const res = await api('notes/timeline', {
+ limit: 100,
+ withRenotes: false,
+ }, alice);
- await api('following/create', { userId: bob.id }, alice);
- await api('mute/create', { userId: carol.id }, alice);
- await setTimeout(1000);
- const carolNote = await post(carol, { text: 'hi' });
- const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
+ });
- await waitForPushToTl();
+ test('[withRenotes: false] フォローしているユーザーの他人の投稿のリノートが含まれない', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- const res = await api('notes/timeline', { limit: 100 }, alice);
+ await api('following/create', { userId: bob.id }, alice);
+ await setTimeout(250);
+ const carolNote = await post(carol, { text: 'hi' });
+ const bobNote = await post(bob, { renoteId: carolNote.id });
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
- });
+ await waitForPushToTl();
- test.concurrent('withReplies: true でフォローしているユーザーが行ったミュートしているユーザーの投稿への返信が含まれない', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ const res = await api('notes/timeline', {
+ withRenotes: false,
+ }, alice);
- await api('following/create', { userId: bob.id }, alice);
- await api('following/update', { userId: bob.id, withReplies: true }, alice);
- await api('mute/create', { userId: carol.id }, alice);
- await setTimeout(1000);
- const carolNote = await post(carol, { text: 'hi' });
- const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
+ });
- await waitForPushToTl();
+ test('[withRenotes: false] フォローしているユーザーの他人の投稿の引用が含まれる', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- const res = await api('notes/timeline', { limit: 100 }, alice);
+ await api('following/create', { userId: bob.id }, alice);
+ await setTimeout(250);
+ const carolNote = await post(carol, { text: 'hi' });
+ const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id });
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
- });
+ await waitForPushToTl();
- test.concurrent('ミュートしているユーザーのノートの、関係のないユーザによる引用ノートの、フォローしているユーザーによるリノートが含まれない', async () => {
- const [alice, bob, carol, dave] = await Promise.all([signup(), signup(), signup(), signup()]);
+ const res = await api('notes/timeline', {
+ withRenotes: false,
+ }, alice);
- await api('following/create', { userId: bob.id }, alice);
- await api('mute/create', { userId: carol.id }, alice);
- await setTimeout(1000);
- const carolNote = await post(carol, { text: 'hi' });
- const daveNote = await post(dave, { text: 'quote hi', renoteId: carolNote.id });
- const bobNote = await post(bob, { renoteId: daveNote.id });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
+ });
- await waitForPushToTl();
+ test('フォローしているユーザーの他人への visibility: specified なノートが含まれない', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- const res = await api('notes/timeline', { limit: 100 }, alice);
+ await api('following/create', { userId: bob.id }, alice);
+ await setTimeout(250);
+ const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [carol.id] });
- assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
- assert.strictEqual(res.body.some(note => note.id === daveNote.id), false);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- });
+ await waitForPushToTl();
- test.concurrent('ミュートしているユーザーのノートの、関係のないユーザによるリプライの、フォローしているユーザーによるリノートが含まれない', async () => {
- const [alice, bob, carol, dave] = await Promise.all([signup(), signup(), signup(), signup()]);
+ const res = await api('notes/timeline', { limit: 100 }, alice);
- await api('following/create', { userId: bob.id }, alice);
- await api('mute/create', { userId: carol.id }, alice);
- await setTimeout(1000);
- const carolNote = await post(carol, { text: 'hi' });
- const daveNote = await post(dave, { text: 'quote hi', replyId: carolNote.id });
- const bobNote = await post(bob, { renoteId: daveNote.id });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ });
- await waitForPushToTl();
+ test('フォローしているユーザーが行ったミュートしているユーザーのリノートが含まれない', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- const res = await api('notes/timeline', { limit: 100 }, alice);
+ await api('following/create', { userId: bob.id }, alice);
+ await api('mute/create', { userId: carol.id }, alice);
+ await setTimeout(250);
+ const carolNote = await post(carol, { text: 'hi' });
+ const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id });
- assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
- assert.strictEqual(res.body.some(note => note.id === daveNote.id), false);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- });
+ await waitForPushToTl();
- test.concurrent('フォローしているリモートユーザーのノートが含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
+ const res = await api('notes/timeline', { limit: 100 }, alice);
- await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' });
- await api('following/create', { userId: bob.id }, alice);
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
+ });
- const bobNote = await post(bob, { text: 'hi' });
+ test('withReplies: true でフォローしているユーザーが行ったミュートしているユーザーの投稿への返信が含まれない', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- await waitForPushToTl();
+ await api('following/create', { userId: bob.id }, alice);
+ await api('following/update', { userId: bob.id, withReplies: true }, alice);
+ await api('mute/create', { userId: carol.id }, alice);
+ await setTimeout(250);
+ const carolNote = await post(carol, { text: 'hi' });
+ const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
- const res = await api('notes/timeline', { limit: 100 }, alice);
+ await waitForPushToTl();
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- });
+ const res = await api('notes/timeline', { limit: 100 }, alice);
- test.concurrent('フォローしているリモートユーザーの visibility: home なノートが含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
+ });
- await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' });
- await api('following/create', { userId: bob.id }, alice);
+ test('ミュートしているユーザーのノートの、関係のないユーザによる引用ノートの、フォローしているユーザーによるリノートが含まれない', async () => {
+ const [alice, bob, carol, dave] = await Promise.all([signup(), signup(), signup(), signup()]);
- const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
+ await api('following/create', { userId: bob.id }, alice);
+ await api('mute/create', { userId: carol.id }, alice);
+ await setTimeout(250);
+ const carolNote = await post(carol, { text: 'hi' });
+ const daveNote = await post(dave, { text: 'quote hi', renoteId: carolNote.id });
+ const bobNote = await post(bob, { renoteId: daveNote.id });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('notes/timeline', { limit: 100 }, alice);
+ const res = await api('notes/timeline', { limit: 100 }, alice);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- });
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
+ assert.strictEqual(res.body.some(note => note.id === daveNote.id), false);
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ });
- test.concurrent('[withFiles: true] フォローしているユーザーのファイル付きノートのみ含まれる', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ test('ミュートしているユーザーのノートの、関係のないユーザによるリプライの、フォローしているユーザーによるリノートが含まれない', async () => {
+ const [alice, bob, carol, dave] = await Promise.all([signup(), signup(), signup(), signup()]);
- await api('following/create', { userId: bob.id }, alice);
- await setTimeout(1000);
- const [bobFile, carolFile] = await Promise.all([
- uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png'),
- uploadUrl(carol, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png'),
- ]);
- const bobNote1 = await post(bob, { text: 'hi' });
- const bobNote2 = await post(bob, { fileIds: [bobFile.id] });
- const carolNote1 = await post(carol, { text: 'hi' });
- const carolNote2 = await post(carol, { fileIds: [carolFile.id] });
+ await api('following/create', { userId: bob.id }, alice);
+ await api('mute/create', { userId: carol.id }, alice);
+ await setTimeout(250);
+ const carolNote = await post(carol, { text: 'hi' });
+ const daveNote = await post(dave, { text: 'quote hi', replyId: carolNote.id });
+ const bobNote = await post(bob, { renoteId: daveNote.id });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('notes/timeline', { limit: 100, withFiles: true }, alice);
+ const res = await api('notes/timeline', { limit: 100 }, alice);
- assert.strictEqual(res.body.some(note => note.id === bobNote1.id), false);
- assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true);
- assert.strictEqual(res.body.some(note => note.id === carolNote1.id), false);
- assert.strictEqual(res.body.some(note => note.id === carolNote2.id), false);
- }, 1000 * 30);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
+ assert.strictEqual(res.body.some(note => note.id === daveNote.id), false);
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ });
- test.concurrent('フォローしているユーザーのチャンネル投稿が含まれない', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ test('フォローしているリモートユーザーのノートが含まれる', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
- const channel = await api('channels/create', { name: 'channel' }, bob).then(x => x.body);
- await api('following/create', { userId: bob.id }, alice);
- await setTimeout(1000);
- const bobNote = await post(bob, { text: 'hi', channelId: channel.id });
+ await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' });
+ await api('following/create', { userId: bob.id }, alice);
- await waitForPushToTl();
+ const bobNote = await post(bob, { text: 'hi' });
- const res = await api('notes/timeline', { limit: 100 }, alice);
+ await waitForPushToTl();
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- });
+ const res = await api('notes/timeline', { limit: 100 }, alice);
- test.concurrent('自分の visibility: specified なノートが含まれる', async () => {
- const [alice] = await Promise.all([signup()]);
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ });
- const aliceNote = await post(alice, { text: 'hi', visibility: 'specified' });
+ test('フォローしているリモートユーザーの visibility: home なノートが含まれる', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
- await waitForPushToTl();
+ await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' });
+ await api('following/create', { userId: bob.id }, alice);
- const res = await api('notes/timeline', { limit: 100 }, alice);
+ const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
- assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
- assert.strictEqual(res.body.find(note => note.id === aliceNote.id)?.text, 'hi');
- });
+ await waitForPushToTl();
- test.concurrent('フォローしているユーザーの自身を visibleUserIds に指定した visibility: specified なノートが含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ const res = await api('notes/timeline', { limit: 100 }, alice);
- await api('following/create', { userId: bob.id }, alice);
- await setTimeout(1000);
- const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [alice.id] });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ });
- await waitForPushToTl();
+ test('[withFiles: true] フォローしているユーザーのファイル付きノートのみ含まれる', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- const res = await api('notes/timeline', { limit: 100 }, alice);
+ await api('following/create', { userId: bob.id }, alice);
+ await setTimeout(250);
+ const [bobFile, carolFile] = await Promise.all([
+ uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png'),
+ uploadUrl(carol, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png'),
+ ]);
+ const bobNote1 = await post(bob, { text: 'hi' });
+ const bobNote2 = await post(bob, { fileIds: [bobFile.id] });
+ const carolNote1 = await post(carol, { text: 'hi' });
+ const carolNote2 = await post(carol, { fileIds: [carolFile.id] });
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- assert.strictEqual(res.body.find(note => note.id === bobNote.id)?.text, 'hi');
- });
+ await waitForPushToTl();
- test.concurrent('フォローしていないユーザーの自身を visibleUserIds に指定した visibility: specified なノートが含まれない', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ const res = await api('notes/timeline', { limit: 100, withFiles: true }, alice);
- const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [alice.id] });
+ assert.strictEqual(res.body.some(note => note.id === bobNote1.id), false);
+ assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true);
+ assert.strictEqual(res.body.some(note => note.id === carolNote1.id), false);
+ assert.strictEqual(res.body.some(note => note.id === carolNote2.id), false);
+ }, 1000 * 30);
- await waitForPushToTl();
+ test('フォローしているユーザーのチャンネル投稿が含まれない', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup()]);
- const res = await api('notes/timeline', { limit: 100 }, alice);
+ const channel = await api('channels/create', { name: 'channel' }, bob).then(x => x.body);
+ await api('following/create', { userId: bob.id }, alice);
+ await setTimeout(250);
+ const bobNote = await post(bob, { text: 'hi', channelId: channel.id });
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- });
+ await waitForPushToTl();
- test.concurrent('フォローしているユーザーの自身を visibleUserIds に指定していない visibility: specified なノートが含まれない', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ const res = await api('notes/timeline', { limit: 100 }, alice);
- await api('following/create', { userId: bob.id }, alice);
- await setTimeout(1000);
- const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [carol.id] });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ });
- await waitForPushToTl();
+ test('自分の visibility: specified なノートが含まれる', async () => {
+ const [alice] = await Promise.all([signup()]);
- const res = await api('notes/timeline', { limit: 100 }, alice);
+ const aliceNote = await post(alice, { text: 'hi', visibility: 'specified' });
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- });
+ await waitForPushToTl();
- test.concurrent('フォローしていないユーザーからの visibility: specified なノートに返信したときの自身のノートが含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ const res = await api('notes/timeline', { limit: 100 }, alice);
- const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [alice.id] });
- const aliceNote = await post(alice, { text: 'ok', visibility: 'specified', visibleUserIds: [bob.id], replyId: bobNote.id });
+ assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
+ assert.strictEqual(res.body.find(note => note.id === aliceNote.id)?.text, 'hi');
+ });
- await waitForPushToTl();
+ test('フォローしているユーザーの自身を visibleUserIds に指定した visibility: specified なノートが含まれる', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup()]);
- const res = await api('notes/timeline', { limit: 100 }, alice);
+ await api('following/create', { userId: bob.id }, alice);
+ await setTimeout(250);
+ const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [alice.id] });
- assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
- assert.strictEqual(res.body.find(note => note.id === aliceNote.id)?.text, 'ok');
- });
+ await waitForPushToTl();
- /* TODO
- test.concurrent('自身の visibility: specified なノートへのフォローしていないユーザーからの返信が含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ const res = await api('notes/timeline', { limit: 100 }, alice);
- const aliceNote = await post(alice, { text: 'hi', visibility: 'specified', visibleUserIds: [bob.id] });
- const bobNote = await post(bob, { text: 'ok', visibility: 'specified', visibleUserIds: [alice.id], replyId: aliceNote.id });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ assert.strictEqual(res.body.find(note => note.id === bobNote.id)?.text, 'hi');
+ });
- await waitForPushToTl();
+ test('フォローしていないユーザーの自身を visibleUserIds に指定した visibility: specified なノートが含まれない', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup()]);
- const res = await api('notes/timeline', { limit: 100 }, alice);
+ const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [alice.id] });
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- assert.strictEqual(res.body.find(note => note.id === bobNote.id).text, 'ok');
- });
- */
+ await waitForPushToTl();
- // ↑の挙動が理想だけど実装が面倒かも
- test.concurrent('自身の visibility: specified なノートへのフォローしていないユーザーからの返信が含まれない', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ const res = await api('notes/timeline', { limit: 100 }, alice);
- const aliceNote = await post(alice, { text: 'hi', visibility: 'specified', visibleUserIds: [bob.id] });
- const bobNote = await post(bob, { text: 'ok', visibility: 'specified', visibleUserIds: [alice.id], replyId: aliceNote.id });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ });
- await waitForPushToTl();
+ test('フォローしているユーザーの自身を visibleUserIds に指定していない visibility: specified なノートが含まれない', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- const res = await api('notes/timeline', { limit: 100 }, alice);
+ await api('following/create', { userId: bob.id }, alice);
+ await setTimeout(250);
+ const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [carol.id] });
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- });
+ await waitForPushToTl();
- test.concurrent('FTT: ローカルユーザーの HTL にはプッシュされる', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ const res = await api('notes/timeline', { limit: 100 }, alice);
- await api('following/create', {
- userId: alice.id,
- }, bob);
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ });
- const aliceNote = await post(alice, { text: 'I\'m Alice.' });
- const bobNote = await post(bob, { text: 'I\'m Bob.' });
- const carolNote = await post(carol, { text: 'I\'m Carol.' });
+ test('フォローしていないユーザーからの visibility: specified なノートに返信したときの自身のノートが含まれる', async () => {
+ /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ if (!enableFanoutTimeline) return;
- await waitForPushToTl();
+ const [alice, bob] = await Promise.all([signup(), signup()]);
- // NOTE: notes/timeline だと DB へのフォールバックが効くので Redis を直接見て確かめる
- assert.strictEqual(await redisForTimelines.exists(`list:homeTimeline:${bob.id}`), 1);
+ const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [alice.id] });
+ const aliceNote = await post(alice, { text: 'ok', visibility: 'specified', visibleUserIds: [bob.id], replyId: bobNote.id });
- const bobHTL = await redisForTimelines.lrange(`list:homeTimeline:${bob.id}`, 0, -1);
- assert.strictEqual(bobHTL.includes(aliceNote.id), true);
- assert.strictEqual(bobHTL.includes(bobNote.id), true);
- assert.strictEqual(bobHTL.includes(carolNote.id), false);
- });
+ await waitForPushToTl();
- test.concurrent('FTT: リモートユーザーの HTL にはプッシュされない', async () => {
- const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
+ const res = await api('notes/timeline', { limit: 100 }, alice);
- await api('following/create', {
- userId: alice.id,
- }, bob);
+ assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
+ assert.strictEqual(res.body.find(note => note.id === aliceNote.id)?.text, 'ok');
+ });
- await post(alice, { text: 'I\'m Alice.' });
- await post(bob, { text: 'I\'m Bob.' });
+ /* TODO
+ test('自身の visibility: specified なノートへのフォローしていないユーザーからの返信が含まれる', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup()]);
+ const aliceNote = await post(alice, { text: 'hi', visibility: 'specified', visibleUserIds: [bob.id] });
+ const bobNote = await post(bob, { text: 'ok', visibility: 'specified', visibleUserIds: [alice.id], replyId: aliceNote.id });
+ await waitForPushToTl();
+ const res = await api('notes/timeline', { limit: 100 }, alice);
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ assert.strictEqual(res.body.find(note => note.id === bobNote.id).text, 'ok');
+ });
+ */
- await waitForPushToTl();
+ // ↑の挙動が理想だけど実装が面倒かも
+ test('自身の visibility: specified なノートへのフォローしていないユーザーからの返信が含まれない', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup()]);
- // NOTE: notes/timeline だと DB へのフォールバックが効くので Redis を直接見て確かめる
- assert.strictEqual(await redisForTimelines.exists(`list:homeTimeline:${bob.id}`), 0);
- });
- });
+ const aliceNote = await post(alice, { text: 'hi', visibility: 'specified', visibleUserIds: [bob.id] });
+ const bobNote = await post(bob, { text: 'ok', visibility: 'specified', visibleUserIds: [alice.id], replyId: aliceNote.id });
- describe('Local TL', () => {
- test.concurrent('visibility: home なノートが含まれない', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ await waitForPushToTl();
- const carolNote = await post(carol, { text: 'hi', visibility: 'home' });
- const bobNote = await post(bob, { text: 'hi' });
+ const res = await api('notes/timeline', { limit: 100 }, alice);
- await waitForPushToTl();
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ });
- const res = await api('notes/local-timeline', { limit: 100 }, alice);
+ test('FTT: ローカルユーザーの HTL にはプッシュされる', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
- });
+ await api('following/create', {
+ userId: alice.id,
+ }, bob);
- test.concurrent('他人の他人への返信が含まれない', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ const aliceNote = await post(alice, { text: 'I\'m Alice.' });
+ const bobNote = await post(bob, { text: 'I\'m Bob.' });
+ const carolNote = await post(carol, { text: 'I\'m Carol.' });
- const carolNote = await post(carol, { text: 'hi' });
- const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
+ await waitForPushToTl();
- await waitForPushToTl();
+ if (enableFanoutTimeline) {
+ // NOTE: notes/timeline だと DB へのフォールバックが効くので Redis を直接見て確かめる
+ assert.strictEqual(await redisForTimelines.exists(`list:homeTimeline:${bob.id}`), 1);
- const res = await api('notes/local-timeline', { limit: 100 }, alice);
+ const bobHTL = await redisForTimelines.lrange(`list:homeTimeline:${bob.id}`, 0, -1);
+ assert.strictEqual(bobHTL.includes(aliceNote.id), true);
+ assert.strictEqual(bobHTL.includes(bobNote.id), true);
+ assert.strictEqual(bobHTL.includes(carolNote.id), false);
+ } else {
+ assert.strictEqual(await redisForTimelines.exists(`list:homeTimeline:${bob.id}`), 0);
+ }
+ });
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- assert.strictEqual(res.body.some(note => note.id === carolNote.id), true);
- });
+ test('FTT: リモートユーザーの HTL にはプッシュされない', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
- test.concurrent('他人のその人自身への返信が含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ await api('following/create', {
+ userId: alice.id,
+ }, bob);
- const bobNote1 = await post(bob, { text: 'hi' });
- const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id });
+ await post(alice, { text: 'I\'m Alice.' });
+ await post(bob, { text: 'I\'m Bob.' });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('notes/local-timeline', { limit: 100 }, alice);
+ // NOTE: notes/timeline だと DB へのフォールバックが効くので Redis を直接見て確かめる
+ assert.strictEqual(await redisForTimelines.exists(`list:homeTimeline:${bob.id}`), 0);
+ });
- assert.strictEqual(res.body.some(note => note.id === bobNote1.id), true);
- assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true);
- });
+ describe('凍結', () => {
+ let alice: SignupResponse, bob: SignupResponse, carol: SignupResponse;
+ let aliceNote: Note, bobNote: Note, carolNote: Note;
- test.concurrent('チャンネル投稿が含まれない', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ beforeAll(async () => {
+ [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- const channel = await api('channels/create', { name: 'channel' }, bob).then(x => x.body);
- const bobNote = await post(bob, { text: 'hi', channelId: channel.id });
+ await api('following/create', { userId: bob.id }, alice);
+ await api('following/create', { userId: carol.id }, alice);
+ aliceNote = await post(alice, { text: 'hi' });
+ bobNote = await post(bob, { text: 'yo' });
+ carolNote = await post(carol, { text: 'kon\'nichiwa' });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('notes/local-timeline', { limit: 100 }, alice);
+ await api('admin/suspend-user', { userId: carol.id }, root);
+ await setTimeout(100);
+ });
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- });
+ test('凍結後に凍結されたユーザーのノートは見えなくなる', async () => {
+ const res = await api('notes/timeline', { limit: 100 }, alice);
+ assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
+ });
- test.concurrent('リモートユーザーのノートが含まれない', async () => {
- const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
+ test('凍結解除後に凍結されていたユーザーのノートは見えるようになる', async () => {
+ await api('admin/unsuspend-user', { userId: carol.id }, root);
+ await setTimeout(100);
- const bobNote = await post(bob, { text: 'hi' });
+ const res = await api('notes/timeline', { limit: 100 }, alice);
- await waitForPushToTl();
+ assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), true);
+ assert.strictEqual(res.body.find(note => note.id === carolNote.id)?.text, 'kon\'nichiwa');
+ });
+ });
- const res = await api('notes/local-timeline', { limit: 100 }, alice);
+ describe('凍結 (Renote)', () => {
+ let alice: SignupResponse, bob: SignupResponse, carol: SignupResponse;
+ let aliceNote: Note, bobNote: Note, carolNote: Note, bobRenote: Note, carolRenote: Note;
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- });
+ beforeAll(async () => {
+ [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- // 含まれても良いと思うけど実装が面倒なので含まれない
- test.concurrent('フォローしているユーザーの visibility: home なノートが含まれない', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ await api('following/create', { userId: bob.id }, alice);
+ await api('following/create', { userId: carol.id }, alice);
+ aliceNote = await post(alice, { text: 'hi' });
+ bobNote = await post(bob, { text: 'yo' });
+ carolNote = await post(carol, { text: 'kon\'nichiwa' });
+ bobRenote = await post(bob, { renoteId: carolNote.id });
+ carolRenote = await post(carol, { renoteId: bobNote.id });
- await api('following/create', { userId: carol.id }, alice);
- await setTimeout(1000);
- const carolNote = await post(carol, { text: 'hi', visibility: 'home' });
- const bobNote = await post(bob, { text: 'hi' });
+ await waitForPushToTl();
- await waitForPushToTl();
+ await api('admin/suspend-user', { userId: carol.id }, root);
+ await setTimeout(100);
+ });
- const res = await api('notes/local-timeline', { limit: 100 }, alice);
+ test('凍結後に凍結されたユーザーに対するRenoteや凍結されたユーザーのRenoteが見えなくなる', async () => {
+ const res = await api('notes/timeline', { limit: 100 }, alice);
+ assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
+ assert.strictEqual(res.body.some(note => note.id === bobRenote.id), false);
+ assert.strictEqual(res.body.some(note => note.id === carolRenote.id), false);
+ });
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
- });
+ test('凍結解除後に凍結されていたユーザーに対するRenoteや凍結されたユーザーのRenoteが見えなくなる', async () => {
+ await api('admin/unsuspend-user', { userId: carol.id }, root);
+ await setTimeout(100);
- test.concurrent('ミュートしているユーザーのノートが含まれない', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ const res = await api('notes/timeline', { limit: 100 }, alice);
- await api('mute/create', { userId: carol.id }, alice);
- await setTimeout(1000);
- const carolNote = await post(carol, { text: 'hi' });
- const bobNote = await post(bob, { text: 'hi' });
+ assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === bobRenote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === carolRenote.id), true);
+ });
+ });
- await waitForPushToTl();
+ describe('凍結(リモート)', () => {
+ let alice: SignupResponse, bob: SignupResponse, carol: SignupResponse;
+ let aliceNote: Note, bobNote: Note, carolNote: Note;
- const res = await api('notes/local-timeline', { limit: 100 }, alice);
+ beforeAll(async () => {
+ [alice, bob, carol] = await Promise.all([signup(), signup({ host: genHost() }), signup({ host: genHost() })]);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
- });
+ await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' });
+ await api('following/create', { userId: bob.id }, alice);
+ await api('following/create', { userId: carol.id }, alice);
+ aliceNote = await post(alice, { text: 'hi' });
+ bobNote = await post(bob, { text: 'yo' });
+ carolNote = await post(carol, { text: 'kon\'nichiwa' });
+
+ await waitForPushToTl();
- test.concurrent('フォローしているユーザーが行ったミュートしているユーザーのリノートが含まれない', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ await api('admin/suspend-user', { userId: carol.id }, root);
+ await setTimeout(100);
+ });
- await api('following/create', { userId: bob.id }, alice);
- await api('mute/create', { userId: carol.id }, alice);
- await setTimeout(1000);
- const carolNote = await post(carol, { text: 'hi' });
- const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id });
+ test('凍結後に凍結されたユーザーのノートは見えなくなる', async () => {
+ const res = await api('notes/timeline', { limit: 100 }, alice);
- await waitForPushToTl();
+ assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
+ });
- const res = await api('notes/local-timeline', { limit: 100 }, alice);
+ test('凍結解除後に凍結されていたユーザーのノートは見えるようになる', async () => {
+ await api('admin/unsuspend-user', { userId: carol.id }, root);
+ await setTimeout(100);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
+ const res = await api('notes/timeline', { limit: 100 }, alice);
+
+ assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), true);
+ });
+ });
});
- test.concurrent('withReplies: true でフォローしているユーザーが行ったミュートしているユーザーの投稿への返信が含まれない', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ describe('Local TL', () => {
+ test('visibility: home なノートが含まれない', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- await api('following/create', { userId: bob.id }, alice);
- await api('following/update', { userId: bob.id, withReplies: true }, alice);
- await api('mute/create', { userId: carol.id }, alice);
- await setTimeout(1000);
- const carolNote = await post(carol, { text: 'hi' });
- const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
+ const carolNote = await post(carol, { text: 'hi', visibility: 'home' });
+ const bobNote = await post(bob, { text: 'hi' });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('notes/local-timeline', { limit: 100 }, alice);
+ const res = await api('notes/local-timeline', { limit: 100 }, alice);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
- });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
+ });
- test.concurrent('ミュートしているユーザーのノートの、関係のないユーザによる引用ノートの、リノートが含まれない', async () => {
- const [alice, bob, carol, dave] = await Promise.all([signup(), signup(), signup(), signup()]);
+ test('他人の他人への返信が含まれない', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- await api('mute/create', { userId: carol.id }, alice);
- await setTimeout(1000);
- const carolNote = await post(carol, { text: 'hi' });
- const daveNote = await post(dave, { text: 'quote hi', renoteId: carolNote.id });
- const bobNote = await post(bob, { renoteId: daveNote.id });
+ const carolNote = await post(carol, { text: 'hi' });
+ const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('notes/local-timeline', { limit: 100 }, alice);
+ const res = await api('notes/local-timeline', { limit: 100 }, alice);
- assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
- assert.strictEqual(res.body.some(note => note.id === daveNote.id), false);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), true);
+ });
- test.concurrent('ミュートしているユーザーのノートの、関係のないユーザによるリプライの、リノートが含まれない', async () => {
- const [alice, bob, carol, dave] = await Promise.all([signup(), signup(), signup(), signup()]);
+ test('他人のその人自身への返信が含まれる', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup()]);
- await api('mute/create', { userId: carol.id }, alice);
- await setTimeout(1000);
- const carolNote = await post(carol, { text: 'hi' });
- const daveNote = await post(dave, { text: 'quote hi', replyId: carolNote.id });
- const bobNote = await post(bob, { renoteId: daveNote.id });
+ const bobNote1 = await post(bob, { text: 'hi' });
+ const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('notes/local-timeline', { limit: 100 }, alice);
+ const res = await api('notes/local-timeline', { limit: 100 }, alice);
- assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
- assert.strictEqual(res.body.some(note => note.id === daveNote.id), false);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- });
+ assert.strictEqual(res.body.some(note => note.id === bobNote1.id), true);
+ assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true);
+ });
- test.concurrent('withReplies: false でフォローしているユーザーからの自分への返信が含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ test('チャンネル投稿が含まれない', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup()]);
- await api('following/create', { userId: bob.id }, alice);
- await setTimeout(1000);
- const aliceNote = await post(alice, { text: 'hi' });
- const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id });
+ const channel = await api('channels/create', { name: 'channel' }, bob).then(x => x.body);
+ const bobNote = await post(bob, { text: 'hi', channelId: channel.id });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('notes/local-timeline', { limit: 100 }, alice);
+ const res = await api('notes/local-timeline', { limit: 100 }, alice);
- assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ });
- test.concurrent('withReplies: false でフォローしていないユーザーからの自分への返信が含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ test('リモートユーザーのノートが含まれない', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
- await setTimeout(1000);
- const aliceNote = await post(alice, { text: 'hi' });
- const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id });
+ const bobNote = await post(bob, { text: 'hi' });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('notes/local-timeline', { limit: 100 }, alice);
+ const res = await api('notes/local-timeline', { limit: 100 }, alice);
- assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ });
- test.concurrent('[withReplies: true] 他人の他人への返信が含まれる', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ // 含まれても良いと思うけど実装が面倒なので含まれない
+ test('フォローしているユーザーの visibility: home なノートが含まれない', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- const carolNote = await post(carol, { text: 'hi' });
- const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
+ await api('following/create', { userId: carol.id }, alice);
+ await setTimeout(250);
+ const carolNote = await post(carol, { text: 'hi', visibility: 'home' });
+ const bobNote = await post(bob, { text: 'hi' });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('notes/local-timeline', { limit: 100, withReplies: true }, alice);
+ const res = await api('notes/local-timeline', { limit: 100 }, alice);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
+ });
- test.concurrent('[withFiles: true] ファイル付きノートのみ含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ test('ミュートしているユーザーのノートが含まれない', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png');
- const bobNote1 = await post(bob, { text: 'hi' });
- const bobNote2 = await post(bob, { fileIds: [file.id] });
+ await api('mute/create', { userId: carol.id }, alice);
+ await setTimeout(250);
+ const carolNote = await post(carol, { text: 'hi' });
+ const bobNote = await post(bob, { text: 'hi' });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('notes/local-timeline', { limit: 100, withFiles: true }, alice);
+ const res = await api('notes/local-timeline', { limit: 100 }, alice);
- assert.strictEqual(res.body.some(note => note.id === bobNote1.id), false);
- assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true);
- }, 1000 * 10);
- });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
+ });
- describe('Social TL', () => {
- test.concurrent('ローカルユーザーのノートが含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ test('フォローしているユーザーが行ったミュートしているユーザーのリノートが含まれない', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- const bobNote = await post(bob, { text: 'hi' });
+ await api('following/create', { userId: bob.id }, alice);
+ await api('mute/create', { userId: carol.id }, alice);
+ await setTimeout(250);
+ const carolNote = await post(carol, { text: 'hi' });
+ const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('notes/hybrid-timeline', { limit: 100 }, alice);
+ const res = await api('notes/local-timeline', { limit: 100 }, alice);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
+ });
- test.concurrent('ローカルユーザーの visibility: home なノートが含まれない', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ test('withReplies: true でフォローしているユーザーが行ったミュートしているユーザーの投稿への返信が含まれない', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
+ await api('following/create', { userId: bob.id }, alice);
+ await api('following/update', { userId: bob.id, withReplies: true }, alice);
+ await api('mute/create', { userId: carol.id }, alice);
+ await setTimeout(250);
+ const carolNote = await post(carol, { text: 'hi' });
+ const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('notes/hybrid-timeline', { limit: 100 }, alice);
+ const res = await api('notes/local-timeline', { limit: 100 }, alice);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
+ });
- test.concurrent('フォローしているローカルユーザーの visibility: home なノートが含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ test('ミュートしているユーザーのノートの、関係のないユーザによる引用ノートの、リノートが含まれない', async () => {
+ const [alice, bob, carol, dave] = await Promise.all([signup(), signup(), signup(), signup()]);
- await api('following/create', { userId: bob.id }, alice);
- await setTimeout(1000);
- const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
+ await api('mute/create', { userId: carol.id }, alice);
+ await setTimeout(250);
+ const carolNote = await post(carol, { text: 'hi' });
+ const daveNote = await post(dave, { text: 'quote hi', renoteId: carolNote.id });
+ const bobNote = await post(bob, { renoteId: daveNote.id });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('notes/hybrid-timeline', { limit: 100 }, alice);
+ const res = await api('notes/local-timeline', { limit: 100 }, alice);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- });
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
+ assert.strictEqual(res.body.some(note => note.id === daveNote.id), false);
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ });
- test.concurrent('withReplies: false でフォローしているユーザーからの自分への返信が含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ test('ミュートしているユーザーのノートの、関係のないユーザによるリプライの、リノートが含まれない', async () => {
+ const [alice, bob, carol, dave] = await Promise.all([signup(), signup(), signup(), signup()]);
- await api('following/create', { userId: bob.id }, alice);
- await setTimeout(1000);
- const aliceNote = await post(alice, { text: 'hi' });
- const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id });
+ await api('mute/create', { userId: carol.id }, alice);
+ await setTimeout(250);
+ const carolNote = await post(carol, { text: 'hi' });
+ const daveNote = await post(dave, { text: 'quote hi', replyId: carolNote.id });
+ const bobNote = await post(bob, { renoteId: daveNote.id });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('notes/hybrid-timeline', { limit: 100 }, alice);
+ const res = await api('notes/local-timeline', { limit: 100 }, alice);
- assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- });
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
+ assert.strictEqual(res.body.some(note => note.id === daveNote.id), false);
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ });
- test.concurrent('withReplies: true でフォローしているユーザーの他人の visibility: followers な投稿への返信が含まれない', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ test('withReplies: false でフォローしているユーザーからの自分への返信が含まれる', async () => {
+ /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ if (!enableFanoutTimeline) return;
- await api('following/create', { userId: carol.id }, bob);
- await api('following/create', { userId: bob.id }, alice);
- await api('following/update', { userId: bob.id, withReplies: true }, alice);
- await setTimeout(1000);
- const carolNote = await post(carol, { text: 'hi', visibility: 'followers' });
- const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
+ const [alice, bob] = await Promise.all([signup(), signup()]);
- await waitForPushToTl();
+ await api('following/create', { userId: bob.id }, alice);
+ await setTimeout(250);
+ const aliceNote = await post(alice, { text: 'hi' });
+ const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id });
- const res = await api('notes/hybrid-timeline', { limit: 100 }, alice);
+ await waitForPushToTl();
- assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
- assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
- });
+ const res = await api('notes/local-timeline', { limit: 100 }, alice);
- test.concurrent('withReplies: true でフォローしているユーザーの行った別のフォローしているユーザーの visibility: followers な投稿への返信が含まれる', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ });
- await api('following/create', { userId: bob.id }, alice);
- await api('following/create', { userId: carol.id }, alice);
- await api('following/create', { userId: carol.id }, bob);
- await api('following/update', { userId: bob.id, withReplies: true }, alice);
- await setTimeout(1000);
- const carolNote = await post(carol, { text: 'hi', visibility: 'followers' });
- const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
+ test('withReplies: false でフォローしていないユーザーからの自分への返信が含まれる', async () => {
+ /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ if (!enableFanoutTimeline) return;
- await waitForPushToTl();
+ const [alice, bob] = await Promise.all([signup(), signup()]);
- const res = await api('notes/hybrid-timeline', { limit: 100 }, alice);
+ await setTimeout(250);
+ const aliceNote = await post(alice, { text: 'hi' });
+ const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id });
- assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
- assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true);
- assert.strictEqual(res.body.find((note: any) => note.id === carolNote.id)?.text, 'hi');
- });
+ await waitForPushToTl();
- test.concurrent('withReplies: true でフォローしているユーザーの自分の visibility: followers な投稿への返信が含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ const res = await api('notes/local-timeline', { limit: 100 }, alice);
- await api('following/create', { userId: bob.id }, alice);
- await api('following/create', { userId: alice.id }, bob);
- await api('following/update', { userId: bob.id, withReplies: true }, alice);
- await setTimeout(1000);
- const aliceNote = await post(alice, { text: 'hi', visibility: 'followers' });
- const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id });
+ assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ });
- await waitForPushToTl();
+ test('[withReplies: true] 他人の他人への返信が含まれる', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- const res = await api('notes/hybrid-timeline', { limit: 100 }, alice);
+ const carolNote = await post(carol, { text: 'hi' });
+ const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
- assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
- assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true);
- });
+ await waitForPushToTl();
- test.concurrent('他人の他人への返信が含まれない', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ const res = await api('notes/local-timeline', { limit: 100, withReplies: true }, alice);
- const carolNote = await post(carol, { text: 'hi' });
- const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ });
- await waitForPushToTl();
+ test('[withFiles: true] ファイル付きノートのみ含まれる', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup()]);
- const res = await api('notes/hybrid-timeline', { limit: 100 }, alice);
+ const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png');
+ const bobNote1 = await post(bob, { text: 'hi' });
+ const bobNote2 = await post(bob, { fileIds: [file.id] });
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- assert.strictEqual(res.body.some(note => note.id === carolNote.id), true);
- });
+ await waitForPushToTl();
- test.concurrent('リモートユーザーのノートが含まれない', async () => {
- const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
+ const res = await api('notes/local-timeline', { limit: 100, withFiles: true }, alice);
- const bobNote = await post(bob, { text: 'hi' });
+ assert.strictEqual(res.body.some(note => note.id === bobNote1.id), false);
+ assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true);
+ }, 1000 * 10);
- await waitForPushToTl();
+ describe('凍結', () => {
+ let alice: SignupResponse, bob: SignupResponse, carol: SignupResponse;
+ let aliceNote: Note, bobNote: Note, carolNote: Note;
- const res = await api('notes/local-timeline', { limit: 100 }, alice);
+ beforeAll(async () => {
+ [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- });
+ aliceNote = await post(alice, { text: 'hi' });
+ bobNote = await post(bob, { text: 'yo' });
+ carolNote = await post(carol, { text: 'kon\'nichiwa' });
- test.concurrent('フォローしているリモートユーザーのノートが含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
+ await waitForPushToTl();
+ });
- await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' });
- await api('following/create', { userId: bob.id }, alice);
+ test('凍結後に凍結されたユーザーのノートは見えなくなる', async () => {
+ await api('admin/suspend-user', { userId: carol.id }, root);
+ await setTimeout(100);
- const bobNote = await post(bob, { text: 'hi' });
+ const res = await api('notes/local-timeline', { limit: 100 }, alice);
- await waitForPushToTl();
+ assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
+ });
- const res = await api('notes/hybrid-timeline', { limit: 100 }, alice);
+ test('凍結解除後に凍結されていたユーザーのノートは見えるようになる', async () => {
+ await api('admin/unsuspend-user', { userId: carol.id }, root);
+ await setTimeout(100);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ const res = await api('notes/local-timeline', { limit: 100 }, alice);
+
+ assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), true);
+ assert.strictEqual(res.body.find(note => note.id === carolNote.id)?.text, 'kon\'nichiwa');
+ });
+ });
});
- test.concurrent('フォローしているリモートユーザーの visibility: home なノートが含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
+ describe('Social TL', () => {
+ test('ローカルユーザーのノートが含まれる', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup()]);
- await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' });
- await api('following/create', { userId: bob.id }, alice);
+ const bobNote = await post(bob, { text: 'hi' });
- const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
+ await waitForPushToTl();
- await waitForPushToTl();
+ const res = await api('notes/hybrid-timeline', { limit: 100 }, alice);
- const res = await api('notes/hybrid-timeline', { limit: 100 }, alice);
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ });
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- });
+ test('ローカルユーザーの visibility: home なノートが含まれない', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup()]);
- test.concurrent('withReplies: false でフォローしていないユーザーからの自分への返信が含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
- await setTimeout(1000);
- const aliceNote = await post(alice, { text: 'hi' });
- const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id });
+ await waitForPushToTl();
- await waitForPushToTl();
+ const res = await api('notes/hybrid-timeline', { limit: 100 }, alice);
- const res = await api('notes/local-timeline', { limit: 100 }, alice);
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ });
- assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- });
+ test('フォローしているローカルユーザーの visibility: home なノートが含まれる', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup()]);
- test.concurrent('[withReplies: true] 他人の他人への返信が含まれる', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ await api('following/create', { userId: bob.id }, alice);
+ await setTimeout(250);
+ const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
- const carolNote = await post(carol, { text: 'hi' });
- const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
+ await waitForPushToTl();
- await waitForPushToTl();
+ const res = await api('notes/hybrid-timeline', { limit: 100 }, alice);
- const res = await api('notes/hybrid-timeline', { limit: 100, withReplies: true }, alice);
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ });
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- });
+ test('withReplies: false でフォローしているユーザーからの自分への返信が含まれる', async () => {
+ /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ if (!enableFanoutTimeline) return;
- test.concurrent('[withFiles: true] ファイル付きノートのみ含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ const [alice, bob] = await Promise.all([signup(), signup()]);
- const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png');
- const bobNote1 = await post(bob, { text: 'hi' });
- const bobNote2 = await post(bob, { fileIds: [file.id] });
+ await api('following/create', { userId: bob.id }, alice);
+ await setTimeout(250);
+ const aliceNote = await post(alice, { text: 'hi' });
+ const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('notes/hybrid-timeline', { limit: 100, withFiles: true }, alice);
+ const res = await api('notes/hybrid-timeline', { limit: 100 }, alice);
- assert.strictEqual(res.body.some(note => note.id === bobNote1.id), false);
- assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true);
- }, 1000 * 10);
- });
+ assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ });
- describe('User List TL', () => {
- test.concurrent('リスインしているフォローしていないユーザーのノートが含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ test('withReplies: true でフォローしているユーザーの他人の visibility: followers な投稿への返信が含まれない', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
- await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
- await setTimeout(1000);
- const bobNote = await post(bob, { text: 'hi' });
+ await api('following/create', { userId: carol.id }, bob);
+ await api('following/create', { userId: bob.id }, alice);
+ await api('following/update', { userId: bob.id, withReplies: true }, alice);
+ await setTimeout(250);
+ const carolNote = await post(carol, { text: 'hi', visibility: 'followers' });
+ const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('notes/user-list-timeline', { listId: list.id }, alice);
+ const res = await api('notes/hybrid-timeline', { limit: 100 }, alice);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- });
+ assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
+ assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
+ });
- test.concurrent('リスインしているフォローしていないユーザーの visibility: home なノートが含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ test('withReplies: true でフォローしているユーザーの行った別のフォローしているユーザーの visibility: followers な投稿への返信が含まれる', async () => {
+ /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ if (!enableFanoutTimeline) return;
- const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
- await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
- await setTimeout(1000);
- const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- await waitForPushToTl();
+ await api('following/create', { userId: bob.id }, alice);
+ await api('following/create', { userId: carol.id }, alice);
+ await api('following/create', { userId: carol.id }, bob);
+ await api('following/update', { userId: bob.id, withReplies: true }, alice);
+ await setTimeout(250);
+ const carolNote = await post(carol, { text: 'hi', visibility: 'followers' });
+ const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
- const res = await api('notes/user-list-timeline', { listId: list.id }, alice);
+ await waitForPushToTl();
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- });
+ const res = await api('notes/hybrid-timeline', { limit: 100 }, alice);
- test.concurrent('リスインしているフォローしていないユーザーの visibility: followers なノートが含まれない', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
+ assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true);
+ assert.strictEqual(res.body.find((note: any) => note.id === carolNote.id)?.text, 'hi');
+ });
- const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
- await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
- await setTimeout(1000);
- const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
+ test('withReplies: true でフォローしているユーザーの自分の visibility: followers な投稿への返信が含まれる', async () => {
+ /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ if (!enableFanoutTimeline) return;
- await waitForPushToTl();
+ const [alice, bob] = await Promise.all([signup(), signup()]);
- const res = await api('notes/user-list-timeline', { listId: list.id }, alice);
+ await api('following/create', { userId: bob.id }, alice);
+ await api('following/create', { userId: alice.id }, bob);
+ await api('following/update', { userId: bob.id, withReplies: true }, alice);
+ await setTimeout(250);
+ const aliceNote = await post(alice, { text: 'hi', visibility: 'followers' });
+ const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id });
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- });
+ await waitForPushToTl();
- test.concurrent('リスインしているフォローしていないユーザーの他人への返信が含まれない', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ const res = await api('notes/hybrid-timeline', { limit: 100 }, alice);
- const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
- await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
- await setTimeout(1000);
- const carolNote = await post(carol, { text: 'hi' });
- const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
+ assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
+ assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true);
+ });
- await waitForPushToTl();
+ test('他人の他人への返信が含まれない', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- const res = await api('notes/user-list-timeline', { listId: list.id }, alice);
+ const carolNote = await post(carol, { text: 'hi' });
+ const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- });
+ await waitForPushToTl();
- test.concurrent('リスインしているフォローしていないユーザーのユーザー自身への返信が含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ const res = await api('notes/hybrid-timeline', { limit: 100 }, alice);
- const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
- await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
- await setTimeout(1000);
- const bobNote1 = await post(bob, { text: 'hi' });
- const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), true);
+ });
- await waitForPushToTl();
+ test('リモートユーザーのノートが含まれない', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
- const res = await api('notes/user-list-timeline', { listId: list.id }, alice);
+ const bobNote = await post(bob, { text: 'hi' });
- assert.strictEqual(res.body.some(note => note.id === bobNote1.id), true);
- assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true);
- });
+ await waitForPushToTl();
- test.concurrent('withReplies: false でリスインしているフォローしていないユーザーからの自分への返信が含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ const res = await api('notes/local-timeline', { limit: 100 }, alice);
- const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
- await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
- await api('users/lists/update-membership', { listId: list.id, userId: bob.id, withReplies: false }, alice);
- await setTimeout(1000);
- const aliceNote = await post(alice, { text: 'hi' });
- const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ });
- await waitForPushToTl();
+ test('フォローしているリモートユーザーのノートが含まれる', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
- const res = await api('notes/user-list-timeline', { listId: list.id }, alice);
+ await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' });
+ await api('following/create', { userId: bob.id }, alice);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- });
+ const bobNote = await post(bob, { text: 'hi' });
- test.concurrent('withReplies: false でリスインしているフォローしていないユーザーの他人への返信が含まれない', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ await waitForPushToTl();
- const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
- await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
- await api('users/lists/update-membership', { listId: list.id, userId: bob.id, withReplies: false }, alice);
- await setTimeout(1000);
- const carolNote = await post(carol, { text: 'hi' });
- const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
+ const res = await api('notes/hybrid-timeline', { limit: 100 }, alice);
- await waitForPushToTl();
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ });
- const res = await api('notes/user-list-timeline', { listId: list.id }, alice);
+ test('フォローしているリモートユーザーの visibility: home なノートが含まれる', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- });
+ await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' });
+ await api('following/create', { userId: bob.id }, alice);
- test.concurrent('withReplies: true でリスインしているフォローしていないユーザーの他人への返信が含まれる', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
- const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
- await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
- await api('users/lists/update-membership', { listId: list.id, userId: bob.id, withReplies: true }, alice);
- await setTimeout(1000);
- const carolNote = await post(carol, { text: 'hi' });
- const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
+ await waitForPushToTl();
- await waitForPushToTl();
+ const res = await api('notes/hybrid-timeline', { limit: 100 }, alice);
- const res = await api('notes/user-list-timeline', { listId: list.id }, alice);
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ });
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- });
+ test('withReplies: false でフォローしていないユーザーからの自分への返信が含まれる', async () => {
+ /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ if (!enableFanoutTimeline) return;
- test.concurrent('リスインしているフォローしているユーザーの visibility: home なノートが含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ const [alice, bob] = await Promise.all([signup(), signup()]);
- await api('following/create', { userId: bob.id }, alice);
- const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
- await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
- await setTimeout(1000);
- const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
+ await setTimeout(250);
+ const aliceNote = await post(alice, { text: 'hi' });
+ const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('notes/user-list-timeline', { listId: list.id }, alice);
+ const res = await api('notes/local-timeline', { limit: 100 }, alice);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- });
+ assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ });
- test.concurrent('リスインしているフォローしているユーザーの visibility: followers なノートが含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ test('[withReplies: true] 他人の他人への返信が含まれる', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- await api('following/create', { userId: bob.id }, alice);
- const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
- await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
- await setTimeout(1000);
- const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
+ const carolNote = await post(carol, { text: 'hi' });
+ const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('notes/user-list-timeline', { listId: list.id }, alice);
+ const res = await api('notes/hybrid-timeline', { limit: 100, withReplies: true }, alice);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- assert.strictEqual(res.body.find(note => note.id === bobNote.id)?.text, 'hi');
- });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ });
- test.concurrent('リスインしている自分の visibility: followers なノートが含まれる', async () => {
- const [alice] = await Promise.all([signup(), signup()]);
+ test('[withFiles: true] ファイル付きノートのみ含まれる', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup()]);
- const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
- await api('users/lists/push', { listId: list.id, userId: alice.id }, alice);
- await setTimeout(1000);
- const aliceNote = await post(alice, { text: 'hi', visibility: 'followers' });
+ const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png');
+ const bobNote1 = await post(bob, { text: 'hi' });
+ const bobNote2 = await post(bob, { fileIds: [file.id] });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('notes/user-list-timeline', { listId: list.id }, alice);
+ const res = await api('notes/hybrid-timeline', { limit: 100, withFiles: true }, alice);
- assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
- assert.strictEqual(res.body.find(note => note.id === aliceNote.id)?.text, 'hi');
- });
+ assert.strictEqual(res.body.some(note => note.id === bobNote1.id), false);
+ assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true);
+ }, 1000 * 10);
- test.concurrent('リスインしているユーザーのチャンネルノートが含まれない', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ describe('凍結', () => {
+ /*
+ * bob = 未フォローのローカルユーザー (凍結対象でない)
+ * carol = 未フォローのローカルユーザー (凍結対象)
+ * dave = フォローしているローカルユーザー (凍結対象)
+ */
+ let alice: SignupResponse, bob: SignupResponse, carol: SignupResponse, dave: SignupResponse;
+ let aliceNote: Note, bobNote: Note, carolNote: Note, daveNote: Note;
- const channel = await api('channels/create', { name: 'channel' }, bob).then(x => x.body);
- const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
- await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
- await setTimeout(1000);
- const bobNote = await post(bob, { text: 'hi', channelId: channel.id });
+ beforeAll(async () => {
+ [alice, bob, carol, dave] = await Promise.all([signup(), signup(), signup(), signup()]);
- await waitForPushToTl();
+ await api('following/create', { userId: dave.id }, alice);
+ aliceNote = await post(alice, { text: 'hi' });
+ bobNote = await post(bob, { text: 'yo' });
+ carolNote = await post(carol, { text: 'kon\'nichiwa' });
+ daveNote = await post(dave, { text: 'hello' });
- const res = await api('notes/user-list-timeline', { listId: list.id }, alice);
+ await waitForPushToTl();
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- });
+ await api('admin/suspend-user', { userId: carol.id }, root);
+ await api('admin/suspend-user', { userId: dave.id }, root);
+ await setTimeout(250);
+ });
- test.concurrent('[withFiles: true] リスインしているユーザーのファイル付きノートのみ含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ test('凍結後に凍結されたユーザーのノートは見えなくなる', async () => {
+ const res = await api('notes/hybrid-timeline', { limit: 100 }, alice);
- const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
- await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
- const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png');
- const bobNote1 = await post(bob, { text: 'hi' });
- const bobNote2 = await post(bob, { fileIds: [file.id] });
+ assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
+ assert.strictEqual(res.body.some(note => note.id === daveNote.id), false);
+ });
- await waitForPushToTl();
+ test('凍結解除後に凍結されていたユーザーのノートは見えるようになる', async () => {
+ await api('admin/unsuspend-user', { userId: carol.id }, root);
+ await api('admin/unsuspend-user', { userId: dave.id }, root);
+ await setTimeout(250);
- const res = await api('notes/user-list-timeline', { listId: list.id, withFiles: true }, alice);
+ const res = await api('notes/hybrid-timeline', { limit: 100 }, alice);
- assert.strictEqual(res.body.some(note => note.id === bobNote1.id), false);
- assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true);
- }, 1000 * 10);
+ assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === daveNote.id), true);
+ });
+ });
- test.concurrent('リスインしているユーザーの自身宛ての visibility: specified なノートが含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ describe('凍結 (リモート)', () => {
+ /*
+ * carol = 未フォローのリモートユーザー (凍結対象)
+ * elle = フォローしているリモートユーザー (凍結対象)
+ */
+ let alice: SignupResponse, carol: SignupResponse, elle: SignupResponse;
+ let aliceNote: Note, carolNote: Note, elleNote: Note;
- const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
- await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
- await setTimeout(1000);
- const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [alice.id] });
+ beforeAll(async () => {
+ [alice, carol, elle] = await Promise.all([signup(), signup({ host: genHost() }), signup({ host: genHost() })]);
- await waitForPushToTl();
+ await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' });
+ await api('following/create', { userId: elle.id }, alice);
+ aliceNote = await post(alice, { text: 'hi' });
+ carolNote = await post(carol, { text: 'kon\'nichiwa' });
+ elleNote = await post(elle, { text: 'hi there' });
- const res = await api('notes/user-list-timeline', { listId: list.id }, alice);
+ await waitForPushToTl();
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- assert.strictEqual(res.body.find(note => note.id === bobNote.id)?.text, 'hi');
- });
+ await api('admin/suspend-user', { userId: carol.id }, root);
+ await api('admin/suspend-user', { userId: elle.id }, root);
+ await setTimeout(250);
+ });
- test.concurrent('リスインしているユーザーの自身宛てではない visibility: specified なノートが含まれない', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ test('凍結後に凍結されたユーザーのノートは見えなくなる', async () => {
+ const res = await api('notes/hybrid-timeline', { limit: 100 }, alice);
- const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
- await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
- await api('users/lists/push', { listId: list.id, userId: carol.id }, alice);
- await setTimeout(1000);
- const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [carol.id] });
+ assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
+ assert.strictEqual(res.body.some(note => note.id === elleNote.id), false);
+ });
- await waitForPushToTl();
+ test('凍結解除後に凍結されていたユーザーのノートは見えるようになる', async () => {
+ await api('admin/unsuspend-user', { userId: carol.id }, root);
+ await api('admin/unsuspend-user', { userId: elle.id }, root);
+ await setTimeout(250);
- const res = await api('notes/user-list-timeline', { listId: list.id }, alice);
+ const res = await api('notes/hybrid-timeline', { limit: 100 }, alice);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
+ assert.strictEqual(res.body.some(note => note.id === elleNote.id), true);
+ });
+ });
});
- });
- describe('User TL', () => {
- test.concurrent('ノートが含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ describe('User List TL', () => {
+ test('リスインしているフォローしていないユーザーのノートが含まれる', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup()]);
- const bobNote = await post(bob, { text: 'hi' });
+ const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
+ await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
+ await setTimeout(250);
+ const bobNote = await post(bob, { text: 'hi' });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('users/notes', { userId: bob.id }, alice);
+ const res = await api('notes/user-list-timeline', { listId: list.id }, alice);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ });
- test.concurrent('フォローしていないユーザーの visibility: followers なノートが含まれない', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ test('リスインしているフォローしていないユーザーの visibility: home なノートが含まれる', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup()]);
- const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
+ const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
+ await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
+ await setTimeout(250);
+ const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('users/notes', { userId: bob.id }, alice);
+ const res = await api('notes/user-list-timeline', { listId: list.id }, alice);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ });
- test.concurrent('フォローしているユーザーの visibility: followers なノートが含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ test('リスインしているフォローしていないユーザーの visibility: followers なノートが含まれない', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup()]);
- await api('following/create', { userId: bob.id }, alice);
- await setTimeout(1000);
- const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
+ const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
+ await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
+ await setTimeout(250);
+ const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('users/notes', { userId: bob.id }, alice);
+ const res = await api('notes/user-list-timeline', { listId: list.id }, alice);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- assert.strictEqual(res.body.find(note => note.id === bobNote.id)?.text, 'hi');
- });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ });
- test.concurrent('自身の visibility: followers なノートが含まれる', async () => {
- const [alice] = await Promise.all([signup()]);
+ test('リスインしているフォローしていないユーザーの他人への返信が含まれない', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- const aliceNote = await post(alice, { text: 'hi', visibility: 'followers' });
+ const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
+ await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
+ await setTimeout(250);
+ const carolNote = await post(carol, { text: 'hi' });
+ const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('users/notes', { userId: alice.id }, alice);
+ const res = await api('notes/user-list-timeline', { listId: list.id }, alice);
- assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
- assert.strictEqual(res.body.find(note => note.id === aliceNote.id)?.text, 'hi');
- });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ });
- test.concurrent('チャンネル投稿が含まれない', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ test('リスインしているフォローしていないユーザーのユーザー自身への返信が含まれる', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup()]);
- const channel = await api('channels/create', { name: 'channel' }, bob).then(x => x.body);
- const bobNote = await post(bob, { text: 'hi', channelId: channel.id });
+ const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
+ await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
+ await setTimeout(250);
+ const bobNote1 = await post(bob, { text: 'hi' });
+ const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('users/notes', { userId: bob.id }, alice);
+ const res = await api('notes/user-list-timeline', { listId: list.id }, alice);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- });
+ assert.strictEqual(res.body.some(note => note.id === bobNote1.id), true);
+ assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true);
+ });
- test.concurrent('[withReplies: false] 他人への返信が含まれない', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ test('withReplies: false でリスインしているフォローしていないユーザーからの自分への返信が含まれる', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup()]);
- const carolNote = await post(carol, { text: 'hi' });
- const bobNote1 = await post(bob, { text: 'hi' });
- const bobNote2 = await post(bob, { text: 'hi', replyId: carolNote.id });
+ const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
+ await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
+ await api('users/lists/update-membership', { listId: list.id, userId: bob.id, withReplies: false }, alice);
+ await setTimeout(250);
+ const aliceNote = await post(alice, { text: 'hi' });
+ const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('users/notes', { userId: bob.id }, alice);
+ const res = await api('notes/user-list-timeline', { listId: list.id }, alice);
- assert.strictEqual(res.body.some(note => note.id === bobNote1.id), true);
- assert.strictEqual(res.body.some(note => note.id === bobNote2.id), false);
- });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ });
- test.concurrent('[withReplies: true] 他人への返信が含まれる', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ test('withReplies: false でリスインしているフォローしていないユーザーの他人への返信が含まれない', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- const carolNote = await post(carol, { text: 'hi' });
- const bobNote1 = await post(bob, { text: 'hi' });
- const bobNote2 = await post(bob, { text: 'hi', replyId: carolNote.id });
+ const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
+ await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
+ await api('users/lists/update-membership', { listId: list.id, userId: bob.id, withReplies: false }, alice);
+ await setTimeout(250);
+ const carolNote = await post(carol, { text: 'hi' });
+ const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('users/notes', { userId: bob.id, withReplies: true }, alice);
+ const res = await api('notes/user-list-timeline', { listId: list.id }, alice);
- assert.strictEqual(res.body.some(note => note.id === bobNote1.id), true);
- assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true);
- });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ });
- test.concurrent('[withReplies: true] 他人への visibility: specified な返信が含まれない', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ test('withReplies: true でリスインしているフォローしていないユーザーの他人への返信が含まれる', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- const carolNote = await post(carol, { text: 'hi' });
- const bobNote1 = await post(bob, { text: 'hi' });
- const bobNote2 = await post(bob, { text: 'hi', replyId: carolNote.id, visibility: 'specified' });
+ const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
+ await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
+ await api('users/lists/update-membership', { listId: list.id, userId: bob.id, withReplies: true }, alice);
+ await setTimeout(250);
+ const carolNote = await post(carol, { text: 'hi' });
+ const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('users/notes', { userId: bob.id, withReplies: true }, alice);
+ const res = await api('notes/user-list-timeline', { listId: list.id }, alice);
- assert.strictEqual(res.body.some(note => note.id === bobNote1.id), true);
- assert.strictEqual(res.body.some(note => note.id === bobNote2.id), false);
- });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ });
- test.concurrent('[withFiles: true] ファイル付きノートのみ含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ test('リスインしているフォローしているユーザーの visibility: home なノートが含まれる', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup()]);
- const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png');
- const bobNote1 = await post(bob, { text: 'hi' });
- const bobNote2 = await post(bob, { fileIds: [file.id] });
+ await api('following/create', { userId: bob.id }, alice);
+ const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
+ await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
+ await setTimeout(250);
+ const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('users/notes', { userId: bob.id, withFiles: true }, alice);
+ const res = await api('notes/user-list-timeline', { listId: list.id }, alice);
- assert.strictEqual(res.body.some(note => note.id === bobNote1.id), false);
- assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true);
- }, 1000 * 10);
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ });
- test.concurrent('[withChannelNotes: true] チャンネル投稿が含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ test('リスインしているフォローしているユーザーの visibility: followers なノートが含まれる', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup()]);
- const channel = await api('channels/create', { name: 'channel' }, bob).then(x => x.body);
- const bobNote = await post(bob, { text: 'hi', channelId: channel.id });
+ await api('following/create', { userId: bob.id }, alice);
+ const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
+ await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
+ await setTimeout(250);
+ const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('users/notes', { userId: bob.id, withChannelNotes: true }, alice);
+ const res = await api('notes/user-list-timeline', { listId: list.id }, alice);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ assert.strictEqual(res.body.find(note => note.id === bobNote.id)?.text, 'hi');
+ });
- test.concurrent('[withChannelNotes: true] 他人が取得した場合センシティブチャンネル投稿が含まれない', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ test('リスインしている自分の visibility: followers なノートが含まれる', async () => {
+ const [alice] = await Promise.all([signup(), signup()]);
- const channel = await api('channels/create', { name: 'channel', isSensitive: true }, bob).then(x => x.body);
- const bobNote = await post(bob, { text: 'hi', channelId: channel.id });
+ const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
+ await api('users/lists/push', { listId: list.id, userId: alice.id }, alice);
+ await setTimeout(250);
+ const aliceNote = await post(alice, { text: 'hi', visibility: 'followers' });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('users/notes', { userId: bob.id, withChannelNotes: true }, alice);
+ const res = await api('notes/user-list-timeline', { listId: list.id }, alice);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- });
+ assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
+ assert.strictEqual(res.body.find(note => note.id === aliceNote.id)?.text, 'hi');
+ });
- test.concurrent('[withChannelNotes: true] 自分が取得した場合センシティブチャンネル投稿が含まれる', async () => {
- const [bob] = await Promise.all([signup()]);
+ test('リスインしているユーザーのチャンネルノートが含まれない', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup()]);
- const channel = await api('channels/create', { name: 'channel', isSensitive: true }, bob).then(x => x.body);
- const bobNote = await post(bob, { text: 'hi', channelId: channel.id });
+ const channel = await api('channels/create', { name: 'channel' }, bob).then(x => x.body);
+ const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
+ await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
+ await setTimeout(250);
+ const bobNote = await post(bob, { text: 'hi', channelId: channel.id });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('users/notes', { userId: bob.id, withChannelNotes: true }, bob);
+ const res = await api('notes/user-list-timeline', { listId: list.id }, alice);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
- });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ });
- test.concurrent('ミュートしているユーザーに関連する投稿が含まれない', async () => {
- const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+ test('[withFiles: true] リスインしているユーザーのファイル付きノートのみ含まれる', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup()]);
- await api('mute/create', { userId: carol.id }, alice);
- await setTimeout(1000);
- const carolNote = await post(carol, { text: 'hi' });
- const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id });
+ const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
+ await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
+ const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png');
+ const bobNote1 = await post(bob, { text: 'hi' });
+ const bobNote2 = await post(bob, { fileIds: [file.id] });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('users/notes', { userId: bob.id }, alice);
+ const res = await api('notes/user-list-timeline', { listId: list.id, withFiles: true }, alice);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- });
+ assert.strictEqual(res.body.some(note => note.id === bobNote1.id), false);
+ assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true);
+ }, 1000 * 10);
- test.concurrent('ミュートしているユーザーのノートの、関係のないユーザによる引用ノートの、リノートが含まれない', async () => {
- const [alice, bob, carol, dave] = await Promise.all([signup(), signup(), signup(), signup()]);
+ test('リスインしているユーザーの自身宛ての visibility: specified なノートが含まれる', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup()]);
- await api('mute/create', { userId: carol.id }, alice);
- await setTimeout(1000);
- const carolNote = await post(carol, { text: 'hi' });
- const daveNote = await post(dave, { text: 'quote hi', renoteId: carolNote.id });
- const bobNote = await post(bob, { renoteId: daveNote.id });
+ const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
+ await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
+ await setTimeout(250);
+ const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [alice.id] });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('users/notes', { userId: bob.id, limit: 100 }, alice);
+ const res = await api('notes/user-list-timeline', { listId: list.id }, alice);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ assert.strictEqual(res.body.find(note => note.id === bobNote.id)?.text, 'hi');
+ });
- test.concurrent('ミュートしているユーザーのノートの、関係のないユーザによるリプライの、リノートが含まれない', async () => {
- const [alice, bob, carol, dave] = await Promise.all([signup(), signup(), signup(), signup()]);
+ test('リスインしているユーザーの自身宛てではない visibility: specified なノートが含まれない', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- await api('following/create', { userId: bob.id }, alice);
- await api('mute/create', { userId: carol.id }, alice);
- await setTimeout(1000);
- const carolNote = await post(carol, { text: 'hi' });
- const daveNote = await post(dave, { text: 'quote hi', replyId: carolNote.id });
- const bobNote = await post(bob, { renoteId: daveNote.id });
+ const list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
+ await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
+ await api('users/lists/push', { listId: list.id, userId: carol.id }, alice);
+ await setTimeout(250);
+ const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [carol.id] });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('users/notes', { userId: bob.id, limit: 100 }, alice);
+ const res = await api('notes/user-list-timeline', { listId: list.id }, alice);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ });
- test.concurrent('ミュートしていても userId に指定したユーザーの投稿が含まれる', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ describe('凍結', () => {
+ let alice: SignupResponse, bob: SignupResponse, carol: SignupResponse;
+ let aliceNote: Note, bobNote: Note, carolNote: Note;
+ let list: UserList;
- await api('mute/create', { userId: bob.id }, alice);
- await setTimeout(1000);
- const bobNote1 = await post(bob, { text: 'hi' });
- const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id });
- const bobNote3 = await post(bob, { text: 'hi', renoteId: bobNote1.id });
- const bobNote4 = await post(bob, { renoteId: bobNote2.id });
- const bobNote5 = await post(bob, { renoteId: bobNote3.id });
+ beforeAll(async () => {
+ [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
- await waitForPushToTl();
+ list = await api('users/lists/create', { name: 'list' }, alice).then(res => res.body);
- const res = await api('users/notes', { userId: bob.id }, alice);
+ await api('users/lists/push', { listId: list.id, userId: bob.id }, alice);
+ await api('users/lists/push', { listId: list.id, userId: carol.id }, alice);
+ aliceNote = await post(alice, { text: 'hi' });
+ bobNote = await post(bob, { text: 'yo' });
+ carolNote = await post(carol, { text: 'kon\'nichiwa' });
- assert.strictEqual(res.body.some(note => note.id === bobNote1.id), true);
- assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true);
- assert.strictEqual(res.body.some(note => note.id === bobNote3.id), true);
- assert.strictEqual(res.body.some(note => note.id === bobNote4.id), true);
- assert.strictEqual(res.body.some(note => note.id === bobNote5.id), true);
- });
+ await waitForPushToTl();
+
+ await api('admin/suspend-user', { userId: carol.id }, root);
+ await setTimeout(100);
+ });
- test.concurrent('自身の visibility: specified なノートが含まれる', async () => {
- const [alice] = await Promise.all([signup()]);
+ test('凍結後に凍結されたユーザーのノートは見えなくなる', async () => {
+ const res = await api('notes/user-list-timeline', { listId: list.id }, alice);
- const aliceNote = await post(alice, { text: 'hi', visibility: 'specified' });
+ assert.strictEqual(res.body.some(note => note.id === aliceNote.id), false);
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), false);
+ });
- await waitForPushToTl();
+ test('凍結解除後に凍結されていたユーザーのノートは見えるようになる', async () => {
+ await api('admin/unsuspend-user', { userId: carol.id }, root);
+ await setTimeout(100);
- const res = await api('users/notes', { userId: alice.id, withReplies: true }, alice);
+ const res = await api('notes/user-list-timeline', { listId: list.id }, alice);
- assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === aliceNote.id), false);
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ assert.strictEqual(res.body.some(note => note.id === carolNote.id), true);
+ });
+ });
});
- test.concurrent('visibleUserIds に指定されてない visibility: specified なノートが含まれない', async () => {
- const [alice, bob] = await Promise.all([signup(), signup()]);
+ describe('User TL', () => {
+ test('ノートが含まれる', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup()]);
- const bobNote = await post(bob, { text: 'hi', visibility: 'specified' });
+ const bobNote = await post(bob, { text: 'hi' });
- await waitForPushToTl();
+ await waitForPushToTl();
- const res = await api('users/notes', { userId: bob.id, withReplies: true }, alice);
+ const res = await api('users/notes', { userId: bob.id }, alice);
- assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
- });
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ });
- /** @see https://github.com/misskey-dev/misskey/issues/14000 */
- test.concurrent('FTT: sinceId にキャッシュより古いノートを指定しても、sinceId による絞り込みが正しく動作する', async () => {
- const alice = await signup();
- const noteSince = await post(alice, { text: 'Note where id will be `sinceId`.' });
- const note1 = await post(alice, { text: '1' });
- const note2 = await post(alice, { text: '2' });
- await redisForTimelines.del('list:userTimeline:' + alice.id);
- const note3 = await post(alice, { text: '3' });
+ test('フォローしていないユーザーの visibility: followers なノートが含まれない', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup()]);
- const res = await api('users/notes', { userId: alice.id, sinceId: noteSince.id });
- assert.deepStrictEqual(res.body, [note1, note2, note3]);
- });
+ const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
+
+ await waitForPushToTl();
+
+ const res = await api('users/notes', { userId: bob.id }, alice);
+
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ });
+
+ test('フォローしているユーザーの visibility: followers なノートが含まれる', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup()]);
+
+ await api('following/create', { userId: bob.id }, alice);
+ await setTimeout(250);
+ const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
+
+ await waitForPushToTl();
+
+ const res = await api('users/notes', { userId: bob.id }, alice);
+
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ assert.strictEqual(res.body.find(note => note.id === bobNote.id)?.text, 'hi');
+ });
+
+ test('自身の visibility: followers なノートが含まれる', async () => {
+ const [alice] = await Promise.all([signup()]);
+
+ const aliceNote = await post(alice, { text: 'hi', visibility: 'followers' });
+
+ await waitForPushToTl();
+
+ const res = await api('users/notes', { userId: alice.id }, alice);
+
+ assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
+ assert.strictEqual(res.body.find(note => note.id === aliceNote.id)?.text, 'hi');
+ });
+
+ test('チャンネル投稿が含まれない', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup()]);
+
+ const channel = await api('channels/create', { name: 'channel' }, bob).then(x => x.body);
+ const bobNote = await post(bob, { text: 'hi', channelId: channel.id });
+
+ await waitForPushToTl();
+
+ const res = await api('users/notes', { userId: bob.id }, alice);
+
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ });
+
+ test('[withReplies: false] 他人への返信が含まれない', async () => {
+ /* FIXME: https://github.com/misskey-dev/misskey/issues/12065 */ if (!enableFanoutTimeline) return;
+
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+ const carolNote = await post(carol, { text: 'hi' });
+ const bobNote1 = await post(bob, { text: 'hi' });
+ const bobNote2 = await post(bob, { text: 'hi', replyId: carolNote.id });
+
+ await waitForPushToTl();
+
+ const res = await api('users/notes', { userId: bob.id }, alice);
+
+ assert.strictEqual(res.body.some(note => note.id === bobNote1.id), true);
+ assert.strictEqual(res.body.some(note => note.id === bobNote2.id), false);
+ });
+
+ test('[withReplies: true] 他人への返信が含まれる', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+ const carolNote = await post(carol, { text: 'hi' });
+ const bobNote1 = await post(bob, { text: 'hi' });
+ const bobNote2 = await post(bob, { text: 'hi', replyId: carolNote.id });
+
+ await waitForPushToTl();
+
+ const res = await api('users/notes', { userId: bob.id, withReplies: true }, alice);
+
+ assert.strictEqual(res.body.some(note => note.id === bobNote1.id), true);
+ assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true);
+ });
+
+ test('[withReplies: true] 他人への visibility: specified な返信が含まれない', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+ const carolNote = await post(carol, { text: 'hi' });
+ const bobNote1 = await post(bob, { text: 'hi' });
+ const bobNote2 = await post(bob, { text: 'hi', replyId: carolNote.id, visibility: 'specified' });
+
+ await waitForPushToTl();
+
+ const res = await api('users/notes', { userId: bob.id, withReplies: true }, alice);
+
+ assert.strictEqual(res.body.some(note => note.id === bobNote1.id), true);
+ assert.strictEqual(res.body.some(note => note.id === bobNote2.id), false);
+ });
+
+ test('[withFiles: true] ファイル付きノートのみ含まれる', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup()]);
+
+ const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png');
+ const bobNote1 = await post(bob, { text: 'hi' });
+ const bobNote2 = await post(bob, { fileIds: [file.id] });
+
+ await waitForPushToTl();
+
+ const res = await api('users/notes', { userId: bob.id, withFiles: true }, alice);
+
+ assert.strictEqual(res.body.some(note => note.id === bobNote1.id), false);
+ assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true);
+ }, 1000 * 10);
+
+ test('[withChannelNotes: true] チャンネル投稿が含まれる', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup()]);
+
+ const channel = await api('channels/create', { name: 'channel' }, bob).then(x => x.body);
+ const bobNote = await post(bob, { text: 'hi', channelId: channel.id });
+
+ await waitForPushToTl();
- test.concurrent('FTT: sinceId にキャッシュより古いノートを指定しても、sinceId と untilId による絞り込みが正しく動作する', async () => {
- const alice = await signup();
- const noteSince = await post(alice, { text: 'Note where id will be `sinceId`.' });
- const note1 = await post(alice, { text: '1' });
- const note2 = await post(alice, { text: '2' });
- await redisForTimelines.del('list:userTimeline:' + alice.id);
- const note3 = await post(alice, { text: '3' });
- const noteUntil = await post(alice, { text: 'Note where id will be `untilId`.' });
- await post(alice, { text: '4' });
+ const res = await api('users/notes', { userId: bob.id, withChannelNotes: true }, alice);
- const res = await api('users/notes', { userId: alice.id, sinceId: noteSince.id, untilId: noteUntil.id });
- assert.deepStrictEqual(res.body, [note3, note2, note1]);
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ });
+
+ test('[withChannelNotes: true] 他人が取得した場合センシティブチャンネル投稿が含まれない', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup()]);
+
+ const channel = await api('channels/create', { name: 'channel', isSensitive: true }, bob).then(x => x.body);
+ const bobNote = await post(bob, { text: 'hi', channelId: channel.id });
+
+ await waitForPushToTl();
+
+ const res = await api('users/notes', { userId: bob.id, withChannelNotes: true }, alice);
+
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ });
+
+ test('[withChannelNotes: true] 自分が取得した場合センシティブチャンネル投稿が含まれる', async () => {
+ const [bob] = await Promise.all([signup()]);
+
+ const channel = await api('channels/create', { name: 'channel', isSensitive: true }, bob).then(x => x.body);
+ const bobNote = await post(bob, { text: 'hi', channelId: channel.id });
+
+ await waitForPushToTl();
+
+ const res = await api('users/notes', { userId: bob.id, withChannelNotes: true }, bob);
+
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), true);
+ });
+
+ test('ミュートしているユーザーに関連する投稿が含まれない', async () => {
+ const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+ await api('mute/create', { userId: carol.id }, alice);
+ await setTimeout(250);
+ const carolNote = await post(carol, { text: 'hi' });
+ const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id });
+
+ await waitForPushToTl();
+
+ const res = await api('users/notes', { userId: bob.id }, alice);
+
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ });
+
+ test('ミュートしているユーザーのノートの、関係のないユーザによる引用ノートの、リノートが含まれない', async () => {
+ const [alice, bob, carol, dave] = await Promise.all([signup(), signup(), signup(), signup()]);
+
+ await api('mute/create', { userId: carol.id }, alice);
+ await setTimeout(250);
+ const carolNote = await post(carol, { text: 'hi' });
+ const daveNote = await post(dave, { text: 'quote hi', renoteId: carolNote.id });
+ const bobNote = await post(bob, { renoteId: daveNote.id });
+
+ await waitForPushToTl();
+
+ const res = await api('users/notes', { userId: bob.id, limit: 100 }, alice);
+
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ });
+
+ test('ミュートしているユーザーのノートの、関係のないユーザによるリプライの、リノートが含まれない', async () => {
+ const [alice, bob, carol, dave] = await Promise.all([signup(), signup(), signup(), signup()]);
+
+ await api('following/create', { userId: bob.id }, alice);
+ await api('mute/create', { userId: carol.id }, alice);
+ await setTimeout(250);
+ const carolNote = await post(carol, { text: 'hi' });
+ const daveNote = await post(dave, { text: 'quote hi', replyId: carolNote.id });
+ const bobNote = await post(bob, { renoteId: daveNote.id });
+
+ await waitForPushToTl();
+
+ const res = await api('users/notes', { userId: bob.id, limit: 100 }, alice);
+
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ });
+
+ test('ミュートしていても userId に指定したユーザーの投稿が含まれる', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup()]);
+
+ await api('mute/create', { userId: bob.id }, alice);
+ await setTimeout(250);
+ const bobNote1 = await post(bob, { text: 'hi' });
+ const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id });
+ const bobNote3 = await post(bob, { text: 'hi', renoteId: bobNote1.id });
+ const bobNote4 = await post(bob, { renoteId: bobNote2.id });
+ const bobNote5 = await post(bob, { renoteId: bobNote3.id });
+
+ await waitForPushToTl();
+
+ const res = await api('users/notes', { userId: bob.id }, alice);
+
+ assert.strictEqual(res.body.some(note => note.id === bobNote1.id), true);
+ assert.strictEqual(res.body.some(note => note.id === bobNote2.id), true);
+ assert.strictEqual(res.body.some(note => note.id === bobNote3.id), true);
+ assert.strictEqual(res.body.some(note => note.id === bobNote4.id), true);
+ assert.strictEqual(res.body.some(note => note.id === bobNote5.id), true);
+ });
+
+ test('自身の visibility: specified なノートが含まれる', async () => {
+ const [alice] = await Promise.all([signup()]);
+
+ const aliceNote = await post(alice, { text: 'hi', visibility: 'specified' });
+
+ await waitForPushToTl();
+
+ const res = await api('users/notes', { userId: alice.id, withReplies: true }, alice);
+
+ assert.strictEqual(res.body.some(note => note.id === aliceNote.id), true);
+ });
+
+ test('visibleUserIds に指定されてない visibility: specified なノートが含まれない', async () => {
+ const [alice, bob] = await Promise.all([signup(), signup()]);
+
+ const bobNote = await post(bob, { text: 'hi', visibility: 'specified' });
+
+ await waitForPushToTl();
+
+ const res = await api('users/notes', { userId: bob.id, withReplies: true }, alice);
+
+ assert.strictEqual(res.body.some(note => note.id === bobNote.id), false);
+ });
+
+ /** @see https://github.com/misskey-dev/misskey/issues/14000 */
+ test('FTT: sinceId にキャッシュより古いノートを指定しても、sinceId による絞り込みが正しく動作する', async () => {
+ const alice = await signup();
+ const noteSince = await post(alice, { text: 'Note where id will be `sinceId`.' });
+ const note1 = await post(alice, { text: '1' });
+ const note2 = await post(alice, { text: '2' });
+ await redisForTimelines.del('list:userTimeline:' + alice.id);
+ const note3 = await post(alice, { text: '3' });
+
+ const res = await api('users/notes', { userId: alice.id, sinceId: noteSince.id });
+ assert.deepStrictEqual(res.body, [note1, note2, note3]);
+ });
+
+ test('FTT: sinceId にキャッシュより古いノートを指定しても、sinceId と untilId による絞り込みが正しく動作する', async () => {
+ const alice = await signup();
+ const noteSince = await post(alice, { text: 'Note where id will be `sinceId`.' });
+ const note1 = await post(alice, { text: '1' });
+ const note2 = await post(alice, { text: '2' });
+ await redisForTimelines.del('list:userTimeline:' + alice.id);
+ const note3 = await post(alice, { text: '3' });
+ const noteUntil = await post(alice, { text: 'Note where id will be `untilId`.' });
+ await post(alice, { text: '4' });
+
+ const res = await api('users/notes', { userId: alice.id, sinceId: noteSince.id, untilId: noteUntil.id });
+ assert.deepStrictEqual(res.body, [note3, note2, note1]);
+ });
});
- });
- // TODO: リノートミュート済みユーザーのテスト
- // TODO: ページネーションのテスト
+ // TODO: リノートミュート済みユーザーのテスト
+ // TODO: ページネーションのテスト
+ });
});