From 64003f9c7516818ce25491fded4a76856fd0c951 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 21 Aug 2025 04:43:42 +0000 Subject: [PATCH] Fix notification sorting by handling sinceId='0' properly Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com> --- .../backend/src/core/NotificationService.ts | 9 +-- .../backend/test/e2e/notification-sorting.ts | 62 +++++++++++++++++++ 2 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 packages/backend/test/e2e/notification-sorting.ts diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts index eeade4569b..e7554d24e2 100644 --- a/packages/backend/src/core/NotificationService.ts +++ b/packages/backend/src/core/NotificationService.ts @@ -267,7 +267,7 @@ export class NotificationService implements OnApplicationShutdown { excludeTypes?: (MiNotification['type'] | string)[], }, ): Promise { - let sinceTime = sinceId ? this.toXListId(sinceId) : null; + let sinceTime = sinceId && sinceId !== '0' ? this.toXListId(sinceId) : null; let untilTime = untilId ? this.toXListId(untilId) : null; let notifications: MiNotification[]; @@ -275,10 +275,11 @@ export class NotificationService implements OnApplicationShutdown { let notificationsRes: [id: string, fields: string[]][]; // sinceidのみの場合は古い順、そうでない場合は新しい順。 QueryService.makePaginationQueryも参照 - if (sinceTime && !untilTime) { + // sinceId が '0' の場合は最初から古い順で取得 + if ((sinceTime && !untilTime) || (sinceId === '0' && !untilTime)) { notificationsRes = await this.redisClient.xrange( `notificationTimeline:${userId}`, - '(' + sinceTime, + sinceTime ? '(' + sinceTime : '-', '+', 'COUNT', limit); } else { @@ -307,7 +308,7 @@ export class NotificationService implements OnApplicationShutdown { } // フィルタしたことで通知が0件になった場合、次のページを取得する - if (sinceId && !untilId) { + if ((sinceId && !untilId) || (sinceId === '0' && !untilId)) { sinceTime = notificationsRes[notificationsRes.length - 1][0]; } else { untilTime = notificationsRes[notificationsRes.length - 1][0]; diff --git a/packages/backend/test/e2e/notification-sorting.ts b/packages/backend/test/e2e/notification-sorting.ts new file mode 100644 index 0000000000..0840368bb6 --- /dev/null +++ b/packages/backend/test/e2e/notification-sorting.ts @@ -0,0 +1,62 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +process.env.NODE_ENV = 'test'; + +import * as assert from 'assert'; +import { api, post, signup } from '../utils.js'; +import type * as misskey from 'misskey-js'; + +describe('Notification Sorting', () => { + let alice: misskey.entities.SignupResponse; + let bob: misskey.entities.SignupResponse; + + beforeAll(async () => { + alice = await signup({ username: 'alice' }); + bob = await signup({ username: 'bob' }); + }, 1000 * 60 * 2); + + test('notifications are sorted correctly when oldest first is requested', async () => { + // Create some notifications by having Bob mention Alice + const note1 = await post(bob, { text: '@alice first mention' }); + await new Promise(resolve => setTimeout(resolve, 1000)); // Wait 1 second + const note2 = await post(bob, { text: '@alice second mention' }); + await new Promise(resolve => setTimeout(resolve, 1000)); // Wait 1 second + const note3 = await post(bob, { text: '@alice third mention' }); + + // Test newest first (default) + const newestFirstRes = await api('i/notifications', { + includeTypes: ['mention'], + limit: 10, + }, alice); + + assert.strictEqual(newestFirstRes.status, 200); + const newestFirst = newestFirstRes.body.filter(n => n.type === 'mention'); + assert.strictEqual(newestFirst.length >= 3, true); + + // Verify newest first order (note3 should be first) + const newestFirstIds = newestFirst.map(n => n.note.id); + const note3Index = newestFirstIds.indexOf(note3.id); + const note1Index = newestFirstIds.indexOf(note1.id); + assert.strictEqual(note3Index < note1Index, true); + + // Test oldest first with sinceId: '0' + const oldestFirstRes = await api('i/notifications', { + includeTypes: ['mention'], + sinceId: '0', + limit: 10, + }, alice); + + assert.strictEqual(oldestFirstRes.status, 200); + const oldestFirst = oldestFirstRes.body.filter(n => n.type === 'mention'); + assert.strictEqual(oldestFirst.length >= 3, true); + + // Verify oldest first order (note1 should be first) + const oldestFirstIds = oldestFirst.map(n => n.note.id); + const note1IndexOldest = oldestFirstIds.indexOf(note1.id); + const note3IndexOldest = oldestFirstIds.indexOf(note3.id); + assert.strictEqual(note1IndexOldest < note3IndexOldest, true); + }); +}); \ No newline at end of file