From efda2e9baa54945391a7c7c155e67e9ffc67a82e Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Sat, 2 Mar 2024 18:34:49 +0900 Subject: [PATCH 001/192] Update README.md --- README.md | 41 ++++------------------------------------- 1 file changed, 4 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 6fa804f1fa83..24013a7bd817 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@
- Misskey logo + Misskey logo -**🌎 **[Misskey](https://misskey-hub.net/)** is an open source, decentralized social media platform that's free forever! 🚀** +**🌎 **Misskey** is an open source, federated social media platform that's free forever! 🚀** + +[Learn more](https://misskey-hub.net/) --- @@ -22,41 +24,6 @@ become a patron ---- - -[![codecov](https://codecov.io/gh/misskey-dev/misskey/branch/develop/graph/badge.svg?token=R6IQZ3QJOL)](https://codecov.io/gh/misskey-dev/misskey) - -
- -
- - - -## ✨ Features -- **ActivityPub support**\ -Not on Misskey? No problem! Not only can Misskey instances talk to each other, but you can make friends with people on other networks like Mastodon and Pixelfed! -- **Reactions**\ -You can add emoji reactions to any post! No longer are you bound by a like button, show everyone exactly how you feel with the tap of a button. -- **Drive**\ -With Misskey's built in drive, you get cloud storage right in your social media, where you can upload any files, make folders, and find media from posts you've made! -- **Rich Web UI**\ - Misskey has a rich and easy to use Web UI! - It is highly customizable, from changing the layout and adding widgets to making custom themes. - Furthermore, plugins can be created using AiScript, an original programming language. -- And much more... - -
- -
- -## Documentation - -Misskey Documentation can be found at [Misskey Hub](https://misskey-hub.net/docs/), some of the links and graphics above also lead to specific portions of it. - -## Sponsors - -
- RSS3
## Thanks From 38837bd388621f5ba49bd7bf0f657a08ec2f19c4 Mon Sep 17 00:00:00 2001 From: zyoshoka <107108195+zyoshoka@users.noreply.github.com> Date: Sun, 3 Mar 2024 20:15:35 +0900 Subject: [PATCH 002/192] test(backend): refactor tests (#13499) * test(backend): refactor tests * fix: failed test --- packages/backend/test/e2e/2fa.ts | 96 ++-- packages/backend/test/e2e/antennas.ts | 116 +++-- packages/backend/test/e2e/api-visibility.ts | 74 +-- packages/backend/test/e2e/api.ts | 43 +- packages/backend/test/e2e/block.ts | 19 +- packages/backend/test/e2e/clips.ts | 298 ++++++----- packages/backend/test/e2e/drive.ts | 36 +- packages/backend/test/e2e/endpoints.ts | 261 +++++----- packages/backend/test/e2e/exports.ts | 38 +- packages/backend/test/e2e/fetch-resource.ts | 18 +- packages/backend/test/e2e/ff-visibility.ts | 206 ++++---- packages/backend/test/e2e/move.ts | 186 +++---- packages/backend/test/e2e/mute.ts | 87 ++-- packages/backend/test/e2e/note.ts | 169 +++---- packages/backend/test/e2e/renote-mute.ts | 6 +- packages/backend/test/e2e/streaming.ts | 6 +- packages/backend/test/e2e/thread-mute.ts | 18 +- packages/backend/test/e2e/timelines.ts | 336 ++++++------- packages/backend/test/e2e/user-notes.ts | 8 +- packages/backend/test/e2e/users.ts | 469 +++++++++--------- .../backend/test/unit/AnnouncementService.ts | 2 +- .../test/unit/FetchInstanceMetadataService.ts | 19 +- packages/backend/test/unit/RoleService.ts | 3 + packages/backend/test/utils.ts | 61 ++- 24 files changed, 1274 insertions(+), 1301 deletions(-) diff --git a/packages/backend/test/e2e/2fa.ts b/packages/backend/test/e2e/2fa.ts index 87a3c227d64d..13c56b88a693 100644 --- a/packages/backend/test/e2e/2fa.ts +++ b/packages/backend/test/e2e/2fa.ts @@ -187,7 +187,7 @@ describe('2要素認証', () => { }, 1000 * 60 * 2); test('が設定でき、OTPでログインできる。', async () => { - const registerResponse = await api('/i/2fa/register', { + const registerResponse = await api('i/2fa/register', { password, }, alice); assert.strictEqual(registerResponse.status, 200); @@ -197,18 +197,18 @@ describe('2要素認証', () => { assert.strictEqual(registerResponse.body.label, username); assert.strictEqual(registerResponse.body.issuer, config.host); - const doneResponse = await api('/i/2fa/done', { + const doneResponse = await api('i/2fa/done', { token: otpToken(registerResponse.body.secret), }, alice); assert.strictEqual(doneResponse.status, 200); - const usersShowResponse = await api('/users/show', { + const usersShowResponse = await api('users/show', { username, }, alice); assert.strictEqual(usersShowResponse.status, 200); assert.strictEqual(usersShowResponse.body.twoFactorEnabled, true); - const signinResponse = await api('/signin', { + const signinResponse = await api('signin', { ...signinParam(), token: otpToken(registerResponse.body.secret), }); @@ -216,24 +216,24 @@ describe('2要素認証', () => { assert.notEqual(signinResponse.body.i, undefined); // 後片付け - await api('/i/2fa/unregister', { + await api('i/2fa/unregister', { password, token: otpToken(registerResponse.body.secret), }, alice); }); test('が設定でき、セキュリティキーでログインできる。', async () => { - const registerResponse = await api('/i/2fa/register', { + const registerResponse = await api('i/2fa/register', { password, }, alice); assert.strictEqual(registerResponse.status, 200); - const doneResponse = await api('/i/2fa/done', { + const doneResponse = await api('i/2fa/done', { token: otpToken(registerResponse.body.secret), }, alice); assert.strictEqual(doneResponse.status, 200); - const registerKeyResponse = await api('/i/2fa/register-key', { + const registerKeyResponse = await api('i/2fa/register-key', { password, token: otpToken(registerResponse.body.secret), }, alice); @@ -243,23 +243,23 @@ describe('2要素認証', () => { const keyName = 'example-key'; const credentialId = crypto.randomBytes(0x41); - const keyDoneResponse = await api('/i/2fa/key-done', keyDoneParam({ + const keyDoneResponse = await api('i/2fa/key-done', keyDoneParam({ token: otpToken(registerResponse.body.secret), keyName, credentialId, creationOptions: registerKeyResponse.body, - }), alice); + }) as any, alice); assert.strictEqual(keyDoneResponse.status, 200); assert.strictEqual(keyDoneResponse.body.id, credentialId.toString('base64url')); assert.strictEqual(keyDoneResponse.body.name, keyName); - const usersShowResponse = await api('/users/show', { + const usersShowResponse = await api('users/show', { username, }); assert.strictEqual(usersShowResponse.status, 200); assert.strictEqual(usersShowResponse.body.securityKeys, true); - const signinResponse = await api('/signin', { + const signinResponse = await api('signin', { ...signinParam(), }); assert.strictEqual(signinResponse.status, 200); @@ -268,7 +268,7 @@ describe('2要素認証', () => { assert.notEqual(signinResponse.body.allowCredentials, undefined); assert.strictEqual(signinResponse.body.allowCredentials[0].id, credentialId.toString('base64url')); - const signinResponse2 = await api('/signin', signinWithSecurityKeyParam({ + const signinResponse2 = await api('signin', signinWithSecurityKeyParam({ keyName, credentialId, requestOptions: signinResponse.body, @@ -277,24 +277,24 @@ describe('2要素認証', () => { assert.notEqual(signinResponse2.body.i, undefined); // 後片付け - await api('/i/2fa/unregister', { + await api('i/2fa/unregister', { password, token: otpToken(registerResponse.body.secret), }, alice); }); test('が設定でき、セキュリティキーでパスワードレスログインできる。', async () => { - const registerResponse = await api('/i/2fa/register', { + const registerResponse = await api('i/2fa/register', { password, }, alice); assert.strictEqual(registerResponse.status, 200); - const doneResponse = await api('/i/2fa/done', { + const doneResponse = await api('i/2fa/done', { token: otpToken(registerResponse.body.secret), }, alice); assert.strictEqual(doneResponse.status, 200); - const registerKeyResponse = await api('/i/2fa/register-key', { + const registerKeyResponse = await api('i/2fa/register-key', { token: otpToken(registerResponse.body.secret), password, }, alice); @@ -302,33 +302,33 @@ describe('2要素認証', () => { const keyName = 'example-key'; const credentialId = crypto.randomBytes(0x41); - const keyDoneResponse = await api('/i/2fa/key-done', keyDoneParam({ + const keyDoneResponse = await api('i/2fa/key-done', keyDoneParam({ token: otpToken(registerResponse.body.secret), keyName, credentialId, creationOptions: registerKeyResponse.body, - }), alice); + }) as any, alice); assert.strictEqual(keyDoneResponse.status, 200); - const passwordLessResponse = await api('/i/2fa/password-less', { + const passwordLessResponse = await api('i/2fa/password-less', { value: true, }, alice); assert.strictEqual(passwordLessResponse.status, 204); - const usersShowResponse = await api('/users/show', { + const usersShowResponse = await api('users/show', { username, }); assert.strictEqual(usersShowResponse.status, 200); assert.strictEqual(usersShowResponse.body.usePasswordLessLogin, true); - const signinResponse = await api('/signin', { + const signinResponse = await api('signin', { ...signinParam(), password: '', }); assert.strictEqual(signinResponse.status, 200); assert.strictEqual(signinResponse.body.i, undefined); - const signinResponse2 = await api('/signin', { + const signinResponse2 = await api('signin', { ...signinWithSecurityKeyParam({ keyName, credentialId, @@ -340,24 +340,24 @@ describe('2要素認証', () => { assert.notEqual(signinResponse2.body.i, undefined); // 後片付け - await api('/i/2fa/unregister', { + await api('i/2fa/unregister', { password, token: otpToken(registerResponse.body.secret), }, alice); }); test('が設定でき、設定したセキュリティキーの名前を変更できる。', async () => { - const registerResponse = await api('/i/2fa/register', { + const registerResponse = await api('i/2fa/register', { password, }, alice); assert.strictEqual(registerResponse.status, 200); - const doneResponse = await api('/i/2fa/done', { + const doneResponse = await api('i/2fa/done', { token: otpToken(registerResponse.body.secret), }, alice); assert.strictEqual(doneResponse.status, 200); - const registerKeyResponse = await api('/i/2fa/register-key', { + const registerKeyResponse = await api('i/2fa/register-key', { token: otpToken(registerResponse.body.secret), password, }, alice); @@ -365,22 +365,22 @@ describe('2要素認証', () => { const keyName = 'example-key'; const credentialId = crypto.randomBytes(0x41); - const keyDoneResponse = await api('/i/2fa/key-done', keyDoneParam({ + const keyDoneResponse = await api('i/2fa/key-done', keyDoneParam({ token: otpToken(registerResponse.body.secret), keyName, credentialId, creationOptions: registerKeyResponse.body, - }), alice); + }) as any, alice); assert.strictEqual(keyDoneResponse.status, 200); const renamedKey = 'other-key'; - const updateKeyResponse = await api('/i/2fa/update-key', { + const updateKeyResponse = await api('i/2fa/update-key', { name: renamedKey, credentialId: credentialId.toString('base64url'), }, alice); assert.strictEqual(updateKeyResponse.status, 200); - const iResponse = await api('/i', { + const iResponse = await api('i', { }, alice); assert.strictEqual(iResponse.status, 200); const securityKeys = iResponse.body.securityKeysList.filter((s: { id: string; }) => s.id === credentialId.toString('base64url')); @@ -389,24 +389,24 @@ describe('2要素認証', () => { assert.notEqual(securityKeys[0].lastUsed, undefined); // 後片付け - await api('/i/2fa/unregister', { + await api('i/2fa/unregister', { password, token: otpToken(registerResponse.body.secret), }, alice); }); test('が設定でき、設定したセキュリティキーを削除できる。', async () => { - const registerResponse = await api('/i/2fa/register', { + const registerResponse = await api('i/2fa/register', { password, }, alice); assert.strictEqual(registerResponse.status, 200); - const doneResponse = await api('/i/2fa/done', { + const doneResponse = await api('i/2fa/done', { token: otpToken(registerResponse.body.secret), }, alice); assert.strictEqual(doneResponse.status, 200); - const registerKeyResponse = await api('/i/2fa/register-key', { + const registerKeyResponse = await api('i/2fa/register-key', { token: otpToken(registerResponse.body.secret), password, }, alice); @@ -414,20 +414,20 @@ describe('2要素認証', () => { const keyName = 'example-key'; const credentialId = crypto.randomBytes(0x41); - const keyDoneResponse = await api('/i/2fa/key-done', keyDoneParam({ + const keyDoneResponse = await api('i/2fa/key-done', keyDoneParam({ token: otpToken(registerResponse.body.secret), keyName, credentialId, creationOptions: registerKeyResponse.body, - }), alice); + }) as any, alice); assert.strictEqual(keyDoneResponse.status, 200); // テストの実行順によっては複数残ってるので全部消す - const iResponse = await api('/i', { + const iResponse = await api('i', { }, alice); assert.strictEqual(iResponse.status, 200); for (const key of iResponse.body.securityKeysList) { - const removeKeyResponse = await api('/i/2fa/remove-key', { + const removeKeyResponse = await api('i/2fa/remove-key', { token: otpToken(registerResponse.body.secret), password, credentialId: key.id, @@ -435,13 +435,13 @@ describe('2要素認証', () => { assert.strictEqual(removeKeyResponse.status, 200); } - const usersShowResponse = await api('/users/show', { + const usersShowResponse = await api('users/show', { username, }); assert.strictEqual(usersShowResponse.status, 200); assert.strictEqual(usersShowResponse.body.securityKeys, false); - const signinResponse = await api('/signin', { + const signinResponse = await api('signin', { ...signinParam(), token: otpToken(registerResponse.body.secret), }); @@ -449,43 +449,43 @@ describe('2要素認証', () => { assert.notEqual(signinResponse.body.i, undefined); // 後片付け - await api('/i/2fa/unregister', { + await api('i/2fa/unregister', { password, token: otpToken(registerResponse.body.secret), }, alice); }); test('が設定でき、設定解除できる。(パスワードのみでログインできる。)', async () => { - const registerResponse = await api('/i/2fa/register', { + const registerResponse = await api('i/2fa/register', { password, }, alice); assert.strictEqual(registerResponse.status, 200); - const doneResponse = await api('/i/2fa/done', { + const doneResponse = await api('i/2fa/done', { token: otpToken(registerResponse.body.secret), }, alice); assert.strictEqual(doneResponse.status, 200); - const usersShowResponse = await api('/users/show', { + const usersShowResponse = await api('users/show', { username, }); assert.strictEqual(usersShowResponse.status, 200); assert.strictEqual(usersShowResponse.body.twoFactorEnabled, true); - const unregisterResponse = await api('/i/2fa/unregister', { + const unregisterResponse = await api('i/2fa/unregister', { token: otpToken(registerResponse.body.secret), password, }, alice); assert.strictEqual(unregisterResponse.status, 204); - const signinResponse = await api('/signin', { + const signinResponse = await api('signin', { ...signinParam(), }); assert.strictEqual(signinResponse.status, 200); assert.notEqual(signinResponse.body.i, undefined); // 後片付け - await api('/i/2fa/unregister', { + await api('i/2fa/unregister', { password, token: otpToken(registerResponse.body.secret), }, alice); diff --git a/packages/backend/test/e2e/antennas.ts b/packages/backend/test/e2e/antennas.ts index 1a9d5bf1f01f..7370b1963ca3 100644 --- a/packages/backend/test/e2e/antennas.ts +++ b/packages/backend/test/e2e/antennas.ts @@ -7,7 +7,6 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import { DEFAULT_POLICIES } from '@/core/RoleService.js'; -import type { Packed } from '@/misc/json-schema.js'; import { api, failedApiCall, @@ -29,10 +28,7 @@ describe('アンテナ', () => { // エンティティとしてのアンテナを主眼においたテストを記述する // (Antennaを返すエンドポイント、Antennaエンティティを書き換えるエンドポイント、Antennaからノートを取得するエンドポイントをテストする) - // BUG misskey-jsとjson-schemaが一致していない。 - // - srcのenumにgroupが残っている - // - userGroupIdが残っている, isActiveがない - type Antenna = misskey.entities.Antenna | Packed<'Antenna'>; + type Antenna = misskey.entities.Antenna; type User = misskey.entities.SignupResponse; type Note = misskey.entities.Note; @@ -80,7 +76,7 @@ describe('アンテナ', () => { aliceList = await userList(alice, {}); bob = await signup({ username: 'bob' }); aliceList = await userList(alice, {}); - bobFile = (await uploadFile(bob)).body; + bobFile = (await uploadFile(bob)).body!; bobList = await userList(bob); carol = await signup({ username: 'carol' }); await api('users/lists/push', { listId: aliceList.id, userId: bob.id }, alice); @@ -129,9 +125,9 @@ describe('アンテナ', () => { beforeEach(async () => { // テスト間で影響し合わないように毎回全部消す。 for (const user of [alice, bob]) { - const list = await api('/antennas/list', {}, user); + const list = await api('antennas/list', {}, user); for (const antenna of list.body) { - await api('/antennas/delete', { antennaId: antenna.id }, user); + await api('antennas/delete', { antennaId: antenna.id }, user); } } }); @@ -141,11 +137,11 @@ describe('アンテナ', () => { test('が作成できること、キーが過不足なく入っていること。', async () => { const response = await successfulApiCall({ endpoint: 'antennas/create', - parameters: { ...defaultParam }, + parameters: defaultParam, user: alice, }); assert.match(response.id, /[0-9a-z]{10}/); - const expected = { + const expected: Antenna = { id: response.id, caseSensitive: false, createdAt: new Date(response.createdAt).toISOString(), @@ -161,7 +157,7 @@ describe('アンテナ', () => { withFile: false, withReplies: false, localOnly: false, - } as Antenna; + }; assert.deepStrictEqual(response, expected); }); @@ -202,27 +198,27 @@ describe('アンテナ', () => { }); const antennaParamPattern = [ - { parameters: (): object => ({ name: 'x'.repeat(100) }) }, - { parameters: (): object => ({ name: 'x' }) }, - { parameters: (): object => ({ src: 'home' }) }, - { parameters: (): object => ({ src: 'all' }) }, - { parameters: (): object => ({ src: 'users' }) }, - { parameters: (): object => ({ src: 'list' }) }, - { parameters: (): object => ({ userListId: null }) }, - { parameters: (): object => ({ src: 'list', userListId: aliceList.id }) }, - { parameters: (): object => ({ keywords: [['x']] }) }, - { parameters: (): object => ({ keywords: [['a', 'b', 'c'], ['x'], ['y'], ['z']] }) }, - { parameters: (): object => ({ excludeKeywords: [['a', 'b', 'c'], ['x'], ['y'], ['z']] }) }, - { parameters: (): object => ({ users: [alice.username] }) }, - { parameters: (): object => ({ users: [alice.username, bob.username, carol.username] }) }, - { parameters: (): object => ({ caseSensitive: false }) }, - { parameters: (): object => ({ caseSensitive: true }) }, - { parameters: (): object => ({ withReplies: false }) }, - { parameters: (): object => ({ withReplies: true }) }, - { parameters: (): object => ({ withFile: false }) }, - { parameters: (): object => ({ withFile: true }) }, - { parameters: (): object => ({ notify: false }) }, - { parameters: (): object => ({ notify: true }) }, + { parameters: () => ({ name: 'x'.repeat(100) }) }, + { parameters: () => ({ name: 'x' }) }, + { parameters: () => ({ src: 'home' as const }) }, + { parameters: () => ({ src: 'all' as const }) }, + { parameters: () => ({ src: 'users' as const }) }, + { parameters: () => ({ src: 'list' as const }) }, + { parameters: () => ({ userListId: null }) }, + { parameters: () => ({ src: 'list' as const, userListId: aliceList.id }) }, + { parameters: () => ({ keywords: [['x']] }) }, + { parameters: () => ({ keywords: [['a', 'b', 'c'], ['x'], ['y'], ['z']] }) }, + { parameters: () => ({ excludeKeywords: [['a', 'b', 'c'], ['x'], ['y'], ['z']] }) }, + { parameters: () => ({ users: [alice.username] }) }, + { parameters: () => ({ users: [alice.username, bob.username, carol.username] }) }, + { parameters: () => ({ caseSensitive: false }) }, + { parameters: () => ({ caseSensitive: true }) }, + { parameters: () => ({ withReplies: false }) }, + { parameters: () => ({ withReplies: true }) }, + { parameters: () => ({ withFile: false }) }, + { parameters: () => ({ withFile: true }) }, + { parameters: () => ({ notify: false }) }, + { parameters: () => ({ notify: true }) }, ]; test.each(antennaParamPattern)('を作成できること($#)', async ({ parameters }) => { const response = await successfulApiCall({ @@ -335,7 +331,7 @@ describe('アンテナ', () => { test.each([ { label: '全体から', - parameters: (): object => ({ src: 'all' }), + parameters: () => ({ src: 'all' }), posts: [ { note: (): Promise => post(alice, { text: `${keyword}` }), included: true }, { note: (): Promise => post(userFollowedByAlice, { text: `${keyword}` }), included: true }, @@ -346,7 +342,7 @@ describe('アンテナ', () => { { // BUG e4144a1 以降home指定は壊れている(allと同じ) label: 'ホーム指定はallと同じ', - parameters: (): object => ({ src: 'home' }), + parameters: () => ({ src: 'home' }), posts: [ { note: (): Promise => post(alice, { text: `${keyword}` }), included: true }, { note: (): Promise => post(userFollowedByAlice, { text: `${keyword}` }), included: true }, @@ -357,7 +353,7 @@ describe('アンテナ', () => { { // https://github.com/misskey-dev/misskey/issues/9025 label: 'ただし、フォロワー限定投稿とDM投稿を含まない。フォロワーであっても。', - parameters: (): object => ({}), + parameters: () => ({}), posts: [ { note: (): Promise => post(userFollowedByAlice, { text: `${keyword}`, visibility: 'public' }), included: true }, { note: (): Promise => post(userFollowedByAlice, { text: `${keyword}`, visibility: 'home' }), included: true }, @@ -367,56 +363,56 @@ describe('アンテナ', () => { }, { label: 'ブロックしているユーザーのノートは含む', - parameters: (): object => ({}), + parameters: () => ({}), posts: [ { note: (): Promise => post(userBlockedByAlice, { text: `${keyword}` }), included: true }, ], }, { label: 'ブロックされているユーザーのノートは含まない', - parameters: (): object => ({}), + parameters: () => ({}), posts: [ { note: (): Promise => post(userBlockingAlice, { text: `${keyword}` }) }, ], }, { label: 'ミュートしているユーザーのノートは含まない', - parameters: (): object => ({}), + parameters: () => ({}), posts: [ { note: (): Promise => post(userMutedByAlice, { text: `${keyword}` }) }, ], }, { label: 'ミュートされているユーザーのノートは含む', - parameters: (): object => ({}), + parameters: () => ({}), posts: [ { note: (): Promise => post(userMutingAlice, { text: `${keyword}` }), included: true }, ], }, { label: '「見つけやすくする」がOFFのユーザーのノートも含まれる', - parameters: (): object => ({}), + parameters: () => ({}), posts: [ { note: (): Promise => post(userNotExplorable, { text: `${keyword}` }), included: true }, ], }, { label: '鍵付きユーザーのノートも含まれる', - parameters: (): object => ({}), + parameters: () => ({}), posts: [ { note: (): Promise => post(userLocking, { text: `${keyword}` }), included: true }, ], }, { label: 'サイレンスのノートも含まれる', - parameters: (): object => ({}), + parameters: () => ({}), posts: [ { note: (): Promise => post(userSilenced, { text: `${keyword}` }), included: true }, ], }, { label: '削除ユーザーのノートも含まれる', - parameters: (): object => ({}), + parameters: () => ({}), posts: [ { note: (): Promise => post(userDeletedBySelf, { text: `${keyword}` }), included: true }, { note: (): Promise => post(userDeletedByAdmin, { text: `${keyword}` }), included: true }, @@ -424,7 +420,7 @@ describe('アンテナ', () => { }, { label: 'ユーザー指定で', - parameters: (): object => ({ src: 'users', users: [`@${bob.username}`, `@${carol.username}`] }), + parameters: () => ({ src: 'users', users: [`@${bob.username}`, `@${carol.username}`] }), posts: [ { note: (): Promise => post(alice, { text: `test ${keyword}` }) }, { note: (): Promise => post(bob, { text: `test ${keyword}` }), included: true }, @@ -433,7 +429,7 @@ describe('アンテナ', () => { }, { label: 'リスト指定で', - parameters: (): object => ({ src: 'list', userListId: aliceList.id }), + parameters: () => ({ src: 'list', userListId: aliceList.id }), posts: [ { note: (): Promise => post(alice, { text: `test ${keyword}` }) }, { note: (): Promise => post(bob, { text: `test ${keyword}` }), included: true }, @@ -442,14 +438,14 @@ describe('アンテナ', () => { }, { label: 'CWにもマッチする', - parameters: (): object => ({ keywords: [[keyword]] }), + parameters: () => ({ keywords: [[keyword]] }), posts: [ { note: (): Promise => post(bob, { text: 'test', cw: `cw ${keyword}` }), included: true }, ], }, { label: 'キーワード1つ', - parameters: (): object => ({ keywords: [[keyword]] }), + parameters: () => ({ keywords: [[keyword]] }), posts: [ { note: (): Promise => post(alice, { text: 'test' }) }, { note: (): Promise => post(bob, { text: `test ${keyword}` }), included: true }, @@ -458,7 +454,7 @@ describe('アンテナ', () => { }, { label: 'キーワード3つ(AND)', - parameters: (): object => ({ keywords: [['A', 'B', 'C']] }), + parameters: () => ({ keywords: [['A', 'B', 'C']] }), posts: [ { note: (): Promise => post(bob, { text: 'test A' }) }, { note: (): Promise => post(bob, { text: 'test A B' }) }, @@ -469,7 +465,7 @@ describe('アンテナ', () => { }, { label: 'キーワード3つ(OR)', - parameters: (): object => ({ keywords: [['A'], ['B'], ['C']] }), + parameters: () => ({ keywords: [['A'], ['B'], ['C']] }), posts: [ { note: (): Promise => post(bob, { text: 'test' }) }, { note: (): Promise => post(bob, { text: 'test A' }), included: true }, @@ -482,7 +478,7 @@ describe('アンテナ', () => { }, { label: '除外ワード3つ(AND)', - parameters: (): object => ({ excludeKeywords: [['A', 'B', 'C']] }), + parameters: () => ({ excludeKeywords: [['A', 'B', 'C']] }), posts: [ { note: (): Promise => post(bob, { text: `test ${keyword}` }), included: true }, { note: (): Promise => post(bob, { text: `test ${keyword} A` }), included: true }, @@ -495,7 +491,7 @@ describe('アンテナ', () => { }, { label: '除外ワード3つ(OR)', - parameters: (): object => ({ excludeKeywords: [['A'], ['B'], ['C']] }), + parameters: () => ({ excludeKeywords: [['A'], ['B'], ['C']] }), posts: [ { note: (): Promise => post(bob, { text: `test ${keyword}` }), included: true }, { note: (): Promise => post(bob, { text: `test ${keyword} A` }) }, @@ -508,7 +504,7 @@ describe('アンテナ', () => { }, { label: 'キーワード1つ(大文字小文字区別する)', - parameters: (): object => ({ keywords: [['KEYWORD']], caseSensitive: true }), + parameters: () => ({ keywords: [['KEYWORD']], caseSensitive: true }), posts: [ { note: (): Promise => post(bob, { text: 'keyword' }) }, { note: (): Promise => post(bob, { text: 'kEyWoRd' }) }, @@ -517,7 +513,7 @@ describe('アンテナ', () => { }, { label: 'キーワード1つ(大文字小文字区別しない)', - parameters: (): object => ({ keywords: [['KEYWORD']], caseSensitive: false }), + parameters: () => ({ keywords: [['KEYWORD']], caseSensitive: false }), posts: [ { note: (): Promise => post(bob, { text: 'keyword' }), included: true }, { note: (): Promise => post(bob, { text: 'kEyWoRd' }), included: true }, @@ -526,7 +522,7 @@ describe('アンテナ', () => { }, { label: '除外ワード1つ(大文字小文字区別する)', - parameters: (): object => ({ excludeKeywords: [['KEYWORD']], caseSensitive: true }), + parameters: () => ({ excludeKeywords: [['KEYWORD']], caseSensitive: true }), posts: [ { note: (): Promise => post(bob, { text: `${keyword}` }), included: true }, { note: (): Promise => post(bob, { text: `${keyword} keyword` }), included: true }, @@ -536,7 +532,7 @@ describe('アンテナ', () => { }, { label: '除外ワード1つ(大文字小文字区別しない)', - parameters: (): object => ({ excludeKeywords: [['KEYWORD']], caseSensitive: false }), + parameters: () => ({ excludeKeywords: [['KEYWORD']], caseSensitive: false }), posts: [ { note: (): Promise => post(bob, { text: `${keyword}` }), included: true }, { note: (): Promise => post(bob, { text: `${keyword} keyword` }) }, @@ -546,7 +542,7 @@ describe('アンテナ', () => { }, { label: '添付ファイルを問わない', - parameters: (): object => ({ withFile: false }), + parameters: () => ({ withFile: false }), posts: [ { note: (): Promise => post(bob, { text: `${keyword}`, fileIds: [bobFile.id] }), included: true }, { note: (): Promise => post(bob, { text: `${keyword}` }), included: true }, @@ -554,7 +550,7 @@ describe('アンテナ', () => { }, { label: '添付ファイル付きのみ', - parameters: (): object => ({ withFile: true }), + parameters: () => ({ withFile: true }), posts: [ { note: (): Promise => post(bob, { text: `${keyword}`, fileIds: [bobFile.id] }), included: true }, { note: (): Promise => post(bob, { text: `${keyword}` }) }, @@ -562,7 +558,7 @@ describe('アンテナ', () => { }, { label: 'リプライ以外', - parameters: (): object => ({ withReplies: false }), + parameters: () => ({ withReplies: false }), posts: [ { note: (): Promise => post(bob, { text: `${keyword}`, replyId: alicePost.id }) }, { note: (): Promise => post(bob, { text: `${keyword}` }), included: true }, @@ -570,7 +566,7 @@ describe('アンテナ', () => { }, { label: 'リプライも含む', - parameters: (): object => ({ withReplies: true }), + parameters: () => ({ withReplies: true }), posts: [ { note: (): Promise => post(bob, { text: `${keyword}`, replyId: alicePost.id }), included: true }, { note: (): Promise => post(bob, { text: `${keyword}` }), included: true }, @@ -633,7 +629,7 @@ describe('アンテナ', () => { endpoint: 'antennas/notes', parameters: { antennaId: antenna.id, ...paginationParam }, user: alice, - }) as any as Note[]; + }); }, offsetBy, 'desc'); }); diff --git a/packages/backend/test/e2e/api-visibility.ts b/packages/backend/test/e2e/api-visibility.ts index f92384525cf9..c61b0c2a8674 100644 --- a/packages/backend/test/e2e/api-visibility.ts +++ b/packages/backend/test/e2e/api-visibility.ts @@ -6,7 +6,7 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; -import { api, post, signup } from '../utils.js'; +import { UserToken, api, post, signup } from '../utils.js'; import type * as misskey from 'misskey-js'; describe('API visibility', () => { @@ -24,38 +24,38 @@ describe('API visibility', () => { let target2: misskey.entities.SignupResponse; /** public-post */ - let pub: any; + let pub: misskey.entities.Note; /** home-post */ - let home: any; + let home: misskey.entities.Note; /** followers-post */ - let fol: any; + let fol: misskey.entities.Note; /** specified-post */ - let spe: any; + let spe: misskey.entities.Note; /** public-reply to target's post */ - let pubR: any; + let pubR: misskey.entities.Note; /** home-reply to target's post */ - let homeR: any; + let homeR: misskey.entities.Note; /** followers-reply to target's post */ - let folR: any; + let folR: misskey.entities.Note; /** specified-reply to target's post */ - let speR: any; + let speR: misskey.entities.Note; /** public-mention to target */ - let pubM: any; + let pubM: misskey.entities.Note; /** home-mention to target */ - let homeM: any; + let homeM: misskey.entities.Note; /** followers-mention to target */ - let folM: any; + let folM: misskey.entities.Note; /** specified-mention to target */ - let speM: any; + let speM: misskey.entities.Note; /** reply target post */ - let tgt: any; + let tgt: misskey.entities.Note; //#endregion - const show = async (noteId: any, by: any) => { - return await api('/notes/show', { + const show = async (noteId: misskey.entities.Note['id'], by?: UserToken) => { + return await api('notes/show', { noteId, }, by); }; @@ -70,7 +70,7 @@ describe('API visibility', () => { target2 = await signup({ username: 'target2' }); // follow alice <= follower - await api('/following/create', { userId: alice.id }, follower); + await api('following/create', { userId: alice.id }, follower); // normal posts pub = await post(alice, { text: 'x', visibility: 'public' }); @@ -111,7 +111,7 @@ describe('API visibility', () => { }); test('[show] public-postを未認証が見れる', async () => { - const res = await show(pub.id, null); + const res = await show(pub.id); assert.strictEqual(res.body.text, 'x'); }); @@ -132,7 +132,7 @@ describe('API visibility', () => { }); test('[show] home-postを未認証が見れる', async () => { - const res = await show(home.id, null); + const res = await show(home.id); assert.strictEqual(res.body.text, 'x'); }); @@ -153,7 +153,7 @@ describe('API visibility', () => { }); test('[show] followers-postを未認証が見れない', async () => { - const res = await show(fol.id, null); + const res = await show(fol.id); assert.strictEqual(res.body.isHidden, true); }); @@ -179,7 +179,7 @@ describe('API visibility', () => { }); test('[show] specified-postを未認証が見れない', async () => { - const res = await show(spe.id, null); + const res = await show(spe.id); assert.strictEqual(res.body.isHidden, true); }); //#endregion @@ -207,7 +207,7 @@ describe('API visibility', () => { }); test('[show] public-replyを未認証が見れる', async () => { - const res = await show(pubR.id, null); + const res = await show(pubR.id); assert.strictEqual(res.body.text, 'x'); }); @@ -233,7 +233,7 @@ describe('API visibility', () => { }); test('[show] home-replyを未認証が見れる', async () => { - const res = await show(homeR.id, null); + const res = await show(homeR.id); assert.strictEqual(res.body.text, 'x'); }); @@ -259,7 +259,7 @@ describe('API visibility', () => { }); test('[show] followers-replyを未認証が見れない', async () => { - const res = await show(folR.id, null); + const res = await show(folR.id); assert.strictEqual(res.body.isHidden, true); }); @@ -290,7 +290,7 @@ describe('API visibility', () => { }); test('[show] specified-replyを未認証が見れない', async () => { - const res = await show(speR.id, null); + const res = await show(speR.id); assert.strictEqual(res.body.isHidden, true); }); //#endregion @@ -318,7 +318,7 @@ describe('API visibility', () => { }); test('[show] public-mentionを未認証が見れる', async () => { - const res = await show(pubM.id, null); + const res = await show(pubM.id); assert.strictEqual(res.body.text, '@target x'); }); @@ -344,7 +344,7 @@ describe('API visibility', () => { }); test('[show] home-mentionを未認証が見れる', async () => { - const res = await show(homeM.id, null); + const res = await show(homeM.id); assert.strictEqual(res.body.text, '@target x'); }); @@ -370,7 +370,7 @@ describe('API visibility', () => { }); test('[show] followers-mentionを未認証が見れない', async () => { - const res = await show(folM.id, null); + const res = await show(folM.id); assert.strictEqual(res.body.isHidden, true); }); @@ -401,28 +401,28 @@ describe('API visibility', () => { }); test('[show] specified-mentionを未認証が見れない', async () => { - const res = await show(speM.id, null); + const res = await show(speM.id); assert.strictEqual(res.body.isHidden, true); }); //#endregion //#region HTL test('[HTL] public-post が 自分が見れる', async () => { - const res = await api('/notes/timeline', { limit: 100 }, alice); + const res = await api('notes/timeline', { limit: 100 }, alice); assert.strictEqual(res.status, 200); const notes = res.body.filter((n: any) => n.id === pub.id); assert.strictEqual(notes[0].text, 'x'); }); test('[HTL] public-post が 非フォロワーから見れない', async () => { - const res = await api('/notes/timeline', { limit: 100 }, other); + const res = await api('notes/timeline', { limit: 100 }, other); assert.strictEqual(res.status, 200); const notes = res.body.filter((n: any) => n.id === pub.id); assert.strictEqual(notes.length, 0); }); test('[HTL] followers-post が フォロワーから見れる', async () => { - const res = await api('/notes/timeline', { limit: 100 }, follower); + const res = await api('notes/timeline', { limit: 100 }, follower); assert.strictEqual(res.status, 200); const notes = res.body.filter((n: any) => n.id === fol.id); assert.strictEqual(notes[0].text, 'x'); @@ -431,21 +431,21 @@ describe('API visibility', () => { //#region RTL test('[replies] followers-reply が フォロワーから見れる', async () => { - const res = await api('/notes/replies', { noteId: tgt.id, limit: 100 }, follower); + const res = await api('notes/replies', { noteId: tgt.id, limit: 100 }, follower); assert.strictEqual(res.status, 200); const notes = res.body.filter((n: any) => n.id === folR.id); assert.strictEqual(notes[0].text, 'x'); }); test('[replies] followers-reply が 非フォロワー (リプライ先ではない) から見れない', async () => { - const res = await api('/notes/replies', { noteId: tgt.id, limit: 100 }, other); + const res = await api('notes/replies', { noteId: tgt.id, limit: 100 }, other); assert.strictEqual(res.status, 200); const notes = res.body.filter((n: any) => n.id === folR.id); assert.strictEqual(notes.length, 0); }); test('[replies] followers-reply が 非フォロワー (リプライ先である) から見れる', async () => { - const res = await api('/notes/replies', { noteId: tgt.id, limit: 100 }, target); + const res = await api('notes/replies', { noteId: tgt.id, limit: 100 }, target); assert.strictEqual(res.status, 200); const notes = res.body.filter((n: any) => n.id === folR.id); assert.strictEqual(notes[0].text, 'x'); @@ -454,14 +454,14 @@ describe('API visibility', () => { //#region MTL test('[mentions] followers-reply が 非フォロワー (リプライ先である) から見れる', async () => { - const res = await api('/notes/mentions', { limit: 100 }, target); + const res = await api('notes/mentions', { limit: 100 }, target); assert.strictEqual(res.status, 200); const notes = res.body.filter((n: any) => n.id === folR.id); assert.strictEqual(notes[0].text, 'x'); }); test('[mentions] followers-mention が 非フォロワー (メンション先である) から見れる', async () => { - const res = await api('/notes/mentions', { limit: 100 }, target); + const res = await api('notes/mentions', { limit: 100 }, target); assert.strictEqual(res.status, 200); const notes = res.body.filter((n: any) => n.id === folM.id); assert.strictEqual(notes[0].text, '@target x'); diff --git a/packages/backend/test/e2e/api.ts b/packages/backend/test/e2e/api.ts index b6eeec99d762..49c6a0636bed 100644 --- a/packages/backend/test/e2e/api.ts +++ b/packages/backend/test/e2e/api.ts @@ -23,32 +23,32 @@ import type * as misskey from 'misskey-js'; describe('API', () => { let alice: misskey.entities.SignupResponse; let bob: misskey.entities.SignupResponse; - let carol: misskey.entities.SignupResponse; beforeAll(async () => { alice = await signup({ username: 'alice' }); bob = await signup({ username: 'bob' }); - carol = await signup({ username: 'carol' }); }, 1000 * 60 * 2); describe('General validation', () => { test('wrong type', async () => { - const res = await api('/test', { + const res = await api('test', { required: true, + // @ts-expect-error string must be string string: 42, }); assert.strictEqual(res.status, 400); }); test('missing require param', async () => { - const res = await api('/test', { + // @ts-expect-error required is required + const res = await api('test', { string: 'a', }); assert.strictEqual(res.status, 400); }); test('invalid misskey:id (empty string)', async () => { - const res = await api('/test', { + const res = await api('test', { required: true, id: '', }); @@ -56,7 +56,7 @@ describe('API', () => { }); test('valid misskey:id', async () => { - const res = await api('/test', { + const res = await api('test', { required: true, id: '8wvhjghbxu', }); @@ -64,7 +64,7 @@ describe('API', () => { }); test('default value', async () => { - const res = await api('/test', { + const res = await api('test', { required: true, string: 'a', }); @@ -73,7 +73,7 @@ describe('API', () => { }); test('can set null even if it has default value', async () => { - const res = await api('/test', { + const res = await api('test', { required: true, nullableDefault: null, }); @@ -82,7 +82,7 @@ describe('API', () => { }); test('cannot set undefined if it has default value', async () => { - const res = await api('/test', { + const res = await api('test', { required: true, nullableDefault: undefined, }); @@ -99,14 +99,14 @@ describe('API', () => { // aliceは管理者、APIを使える await successfulApiCall({ - endpoint: '/admin/get-index-stats', + endpoint: 'admin/get-index-stats', parameters: {}, user: alice, }); // bobは一般ユーザーだからダメ await failedApiCall({ - endpoint: '/admin/get-index-stats', + endpoint: 'admin/get-index-stats', parameters: {}, user: bob, }, { @@ -117,7 +117,7 @@ describe('API', () => { // publicアクセスももちろんダメ await failedApiCall({ - endpoint: '/admin/get-index-stats', + endpoint: 'admin/get-index-stats', parameters: {}, user: undefined, }, { @@ -128,7 +128,7 @@ describe('API', () => { // ごまがしもダメ await failedApiCall({ - endpoint: '/admin/get-index-stats', + endpoint: 'admin/get-index-stats', parameters: {}, user: { token: 'tsukawasete' }, }, { @@ -138,13 +138,13 @@ describe('API', () => { }); await successfulApiCall({ - endpoint: '/admin/get-index-stats', + endpoint: 'admin/get-index-stats', parameters: {}, user: { token: application2 }, }); await failedApiCall({ - endpoint: '/admin/get-index-stats', + endpoint: 'admin/get-index-stats', parameters: {}, user: { token: application }, }, { @@ -154,7 +154,7 @@ describe('API', () => { }); await failedApiCall({ - endpoint: '/admin/get-index-stats', + endpoint: 'admin/get-index-stats', parameters: {}, user: { token: application3 }, }, { @@ -164,7 +164,7 @@ describe('API', () => { }); await failedApiCall({ - endpoint: '/admin/get-index-stats', + endpoint: 'admin/get-index-stats', parameters: {}, user: { token: application4 }, }, { @@ -177,7 +177,7 @@ describe('API', () => { describe('Authentication header', () => { test('一般リクエスト', async () => { await successfulApiCall({ - endpoint: '/admin/get-index-stats', + endpoint: 'admin/get-index-stats', parameters: {}, user: { token: alice.token, @@ -211,7 +211,7 @@ describe('API', () => { describe('tokenエラー応答でWWW-Authenticate headerを送る', () => { describe('invalid_token', () => { test('一般リクエスト', async () => { - const result = await api('/admin/get-index-stats', {}, { + const result = await api('admin/get-index-stats', {}, { token: 'syuilo', bearer: true, }); @@ -246,7 +246,7 @@ describe('API', () => { describe('tokenがないとrealmだけおくる', () => { test('一般リクエスト', async () => { - const result = await api('/admin/get-index-stats', {}); + const result = await api('admin/get-index-stats', {}); assert.strictEqual(result.status, 401); assert.strictEqual(result.headers.get('WWW-Authenticate'), 'Bearer realm="Misskey"'); }); @@ -259,7 +259,8 @@ describe('API', () => { }); test('invalid_request', async () => { - const result = await api('/notes/create', { text: true }, { + // @ts-expect-error text must be string + const result = await api('notes/create', { text: true }, { token: alice.token, bearer: true, }); diff --git a/packages/backend/test/e2e/block.ts b/packages/backend/test/e2e/block.ts index cbd91e6e42ac..e4f798498f56 100644 --- a/packages/backend/test/e2e/block.ts +++ b/packages/backend/test/e2e/block.ts @@ -22,7 +22,7 @@ describe('Block', () => { }, 1000 * 60 * 2); test('Block作成', async () => { - const res = await api('/blocking/create', { + const res = await api('blocking/create', { userId: bob.id, }, alice); @@ -30,7 +30,7 @@ describe('Block', () => { }); test('ブロックされているユーザーをフォローできない', async () => { - const res = await api('/following/create', { userId: alice.id }, bob); + const res = await api('following/create', { userId: alice.id }, bob); assert.strictEqual(res.status, 400); assert.strictEqual(res.body.error.id, 'c4ab57cc-4e41-45e9-bfd9-584f61e35ce0'); @@ -39,7 +39,7 @@ describe('Block', () => { test('ブロックされているユーザーにリアクションできない', async () => { const note = await post(alice, { text: 'hello' }); - const res = await api('/notes/reactions/create', { noteId: note.id, reaction: '👍' }, bob); + const res = await api('notes/reactions/create', { noteId: note.id, reaction: '👍' }, bob); assert.strictEqual(res.status, 400); assert.strictEqual(res.body.error.id, '20ef5475-9f38-4e4c-bd33-de6d979498ec'); @@ -48,7 +48,7 @@ describe('Block', () => { test('ブロックされているユーザーに返信できない', async () => { const note = await post(alice, { text: 'hello' }); - const res = await api('/notes/create', { replyId: note.id, text: 'yo' }, bob); + const res = await api('notes/create', { replyId: note.id, text: 'yo' }, bob); assert.strictEqual(res.status, 400); assert.strictEqual(res.body.error.id, 'b390d7e1-8a5e-46ed-b625-06271cafd3d3'); @@ -57,7 +57,7 @@ describe('Block', () => { test('ブロックされているユーザーのノートをRenoteできない', async () => { const note = await post(alice, { text: 'hello' }); - const res = await api('/notes/create', { renoteId: note.id, text: 'yo' }, bob); + const res = await api('notes/create', { renoteId: note.id, text: 'yo' }, bob); assert.strictEqual(res.status, 400); assert.strictEqual(res.body.error.id, 'b390d7e1-8a5e-46ed-b625-06271cafd3d3'); @@ -72,12 +72,13 @@ describe('Block', () => { const bobNote = await post(bob, { text: 'hi' }); const carolNote = await post(carol, { text: 'hi' }); - const res = await api('/notes/local-timeline', {}, bob); + const res = await api('notes/local-timeline', {}, bob); + const body = res.body as misskey.entities.Note[]; assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); - assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), false); - 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(body.some(note => note.id === aliceNote.id), false); + assert.strictEqual(body.some(note => note.id === bobNote.id), true); + assert.strictEqual(body.some(note => note.id === carolNote.id), true); }); }); diff --git a/packages/backend/test/e2e/clips.ts b/packages/backend/test/e2e/clips.ts index 2cf397e22da9..ba6f9d6a651c 100644 --- a/packages/backend/test/e2e/clips.ts +++ b/packages/backend/test/e2e/clips.ts @@ -6,47 +6,34 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; -import { JTDDataType } from 'ajv/dist/jtd'; import { DEFAULT_POLICIES } from '@/core/RoleService.js'; -import type { Packed } from '@/misc/json-schema.js'; -import { paramDef as CreateParamDef } from '@/server/api/endpoints/clips/create.js'; -import { paramDef as UpdateParamDef } from '@/server/api/endpoints/clips/update.js'; -import { paramDef as DeleteParamDef } from '@/server/api/endpoints/clips/delete.js'; -import { paramDef as ShowParamDef } from '@/server/api/endpoints/clips/show.js'; -import { paramDef as FavoriteParamDef } from '@/server/api/endpoints/clips/favorite.js'; -import { paramDef as UnfavoriteParamDef } from '@/server/api/endpoints/clips/unfavorite.js'; -import { paramDef as AddNoteParamDef } from '@/server/api/endpoints/clips/add-note.js'; -import { paramDef as RemoveNoteParamDef } from '@/server/api/endpoints/clips/remove-note.js'; -import { paramDef as NotesParamDef } from '@/server/api/endpoints/clips/notes.js'; import { api, ApiRequest, failedApiCall, hiddenNote, post, signup, successfulApiCall } from '../utils.js'; +import type * as Misskey from 'misskey-js'; + +type Optional = Pick, K> & Omit; describe('クリップ', () => { - type User = Packed<'User'>; - type Note = Packed<'Note'>; - type Clip = Packed<'Clip'>; - - let alice: User; - let bob: User; - let aliceNote: Note; - let aliceHomeNote: Note; - let aliceFollowersNote: Note; - let aliceSpecifiedNote: Note; - let bobNote: Note; - let bobHomeNote: Note; - let bobFollowersNote: Note; - let bobSpecifiedNote: Note; + let alice: Misskey.entities.SignupResponse; + let bob: Misskey.entities.SignupResponse; + let aliceNote: Misskey.entities.Note; + let aliceHomeNote: Misskey.entities.Note; + let aliceFollowersNote: Misskey.entities.Note; + let aliceSpecifiedNote: Misskey.entities.Note; + let bobNote: Misskey.entities.Note; + let bobHomeNote: Misskey.entities.Note; + let bobFollowersNote: Misskey.entities.Note; + let bobSpecifiedNote: Misskey.entities.Note; const compareBy = (selector: (s: T) => string = (s: T): string => s.id) => (a: T, b: T): number => { return selector(a).localeCompare(selector(b)); }; - type CreateParam = JTDDataType; - const defaultCreate = (): Partial => ({ + const defaultCreate = (): Pick => ({ name: 'test', }); - const create = async (parameters: Partial = {}, request: Partial = {}): Promise => { - const clip = await successfulApiCall({ - endpoint: '/clips/create', + const create = async (parameters: Partial = {}, request: Partial> = {}): Promise => { + const clip = await successfulApiCall({ + endpoint: 'clips/create', parameters: { ...defaultCreate(), ...parameters, @@ -64,17 +51,16 @@ describe('クリップ', () => { return clip; }; - const createMany = async (parameters: Partial, count = 10, user = alice): Promise => { + const createMany = async (parameters: Partial, count = 10, user = alice): Promise => { return await Promise.all([...Array(count)].map((_, i) => create({ name: `test${i}`, ...parameters, }, { user }))); }; - type UpdateParam = JTDDataType; - const update = async (parameters: Partial, request: Partial = {}): Promise => { - const clip = await successfulApiCall({ - endpoint: '/clips/update', + const update = async (parameters: Optional, request: Partial> = {}): Promise => { + const clip = await successfulApiCall({ + endpoint: 'clips/update', parameters: { name: 'updated', ...parameters, @@ -92,41 +78,39 @@ describe('クリップ', () => { return clip; }; - type DeleteParam = JTDDataType; - const deleteClip = async (parameters: DeleteParam, request: Partial = {}): Promise => { - return await successfulApiCall({ - endpoint: '/clips/delete', + const deleteClip = async (parameters: Misskey.entities.ClipsDeleteRequest, request: Partial> = {}): Promise => { + return await successfulApiCall({ + endpoint: 'clips/delete', parameters, user: alice, ...request, }, { status: 204, - }); + }) as any as void; }; - type ShowParam = JTDDataType; - const show = async (parameters: ShowParam, request: Partial = {}): Promise => { - return await successfulApiCall({ - endpoint: '/clips/show', + const show = async (parameters: Misskey.entities.ClipsShowRequest, request: Partial> = {}): Promise => { + return await successfulApiCall({ + endpoint: 'clips/show', parameters, user: alice, ...request, }); }; - const list = async (request: Partial): Promise => { - return successfulApiCall({ - endpoint: '/clips/list', + const list = async (request: Partial>): Promise => { + return successfulApiCall({ + endpoint: 'clips/list', parameters: {}, user: alice, ...request, }); }; - const usersClips = async (request: Partial): Promise => { - return await successfulApiCall({ - endpoint: '/users/clips', - parameters: {}, + const usersClips = async (parameters: Misskey.entities.UsersClipsRequest, request: Partial> = {}): Promise => { + return await successfulApiCall({ + endpoint: 'users/clips', + parameters, user: alice, ...request, }); @@ -136,23 +120,22 @@ describe('クリップ', () => { alice = await signup({ username: 'alice' }); bob = await signup({ username: 'bob' }); - // FIXME: misskey-jsのNoteはoutdatedなので直接変換できない - aliceNote = await post(alice, { text: 'test' }) as any; - aliceHomeNote = await post(alice, { text: 'home only', visibility: 'home' }) as any; - aliceFollowersNote = await post(alice, { text: 'followers only', visibility: 'followers' }) as any; - aliceSpecifiedNote = await post(alice, { text: 'specified only', visibility: 'specified' }) as any; - bobNote = await post(bob, { text: 'test' }) as any; - bobHomeNote = await post(bob, { text: 'home only', visibility: 'home' }) as any; - bobFollowersNote = await post(bob, { text: 'followers only', visibility: 'followers' }) as any; - bobSpecifiedNote = await post(bob, { text: 'specified only', visibility: 'specified' }) as any; + aliceNote = await post(alice, { text: 'test' }); + aliceHomeNote = await post(alice, { text: 'home only', visibility: 'home' }); + aliceFollowersNote = await post(alice, { text: 'followers only', visibility: 'followers' }); + aliceSpecifiedNote = await post(alice, { text: 'specified only', visibility: 'specified' }); + bobNote = await post(bob, { text: 'test' }); + bobHomeNote = await post(bob, { text: 'home only', visibility: 'home' }); + bobFollowersNote = await post(bob, { text: 'followers only', visibility: 'followers' }); + bobSpecifiedNote = await post(bob, { text: 'specified only', visibility: 'specified' }); }, 1000 * 60 * 2); afterEach(async () => { // テスト間で影響し合わないように毎回全部消す。 for (const user of [alice, bob]) { - const list = await api('/clips/list', { limit: 11 }, user); + const list = await api('clips/list', { limit: 11 }, user); for (const clip of list.body) { - await api('/clips/delete', { clipId: clip.id }, user); + await api('clips/delete', { clipId: clip.id }, user); } } }); @@ -177,7 +160,7 @@ describe('クリップ', () => { } await failedApiCall({ - endpoint: '/clips/create', + endpoint: 'clips/create', parameters: defaultCreate(), user: alice, }, { @@ -204,7 +187,8 @@ describe('クリップ', () => { { label: 'descriptionが最大長+1', parameters: { description: 'a'.repeat(2049) } }, ]; test.each(createClipDenyPattern)('の作成は$labelならできない', async ({ parameters }) => failedApiCall({ - endpoint: '/clips/create', + endpoint: 'clips/create', + // @ts-expect-error invalid params parameters: { ...defaultCreate(), ...parameters, @@ -246,15 +230,15 @@ describe('クリップ', () => { code: 'NO_SUCH_CLIP', id: 'b4d92d70-b216-46fa-9a3f-a8c811699257', } }, - { label: '他人のクリップ', user: (): User => bob, assertion: { + { label: '他人のクリップ', user: () => bob, assertion: { code: 'NO_SUCH_CLIP', id: 'b4d92d70-b216-46fa-9a3f-a8c811699257', } }, ...createClipDenyPattern as any, ])('の更新は$labelならできない', async ({ parameters, user, assertion }) => failedApiCall({ - endpoint: '/clips/update', + endpoint: 'clips/update', parameters: { - clipId: (await create({}, { user: (user ?? ((): User => alice))() })).id, + clipId: (await create({}, { user: (user ?? (() => alice))() })).id, name: 'updated', ...parameters, }, @@ -279,14 +263,15 @@ describe('クリップ', () => { code: 'NO_SUCH_CLIP', id: '70ca08ba-6865-4630-b6fb-8494759aa754', } }, - { label: '他人のクリップ', user: (): User => bob, assertion: { + { label: '他人のクリップ', user: () => bob, assertion: { code: 'NO_SUCH_CLIP', id: '70ca08ba-6865-4630-b6fb-8494759aa754', } }, ])('の削除は$labelならできない', async ({ parameters, user, assertion }) => failedApiCall({ - endpoint: '/clips/delete', + endpoint: 'clips/delete', parameters: { - clipId: (await create({}, { user: (user ?? ((): User => alice))() })).id, + // @ts-expect-error clipId must not be null + clipId: (await create({}, { user: (user ?? (() => alice))() })).id, ...parameters, }, user: alice, @@ -306,7 +291,7 @@ describe('クリップ', () => { test('のID指定取得は他人のPrivateなクリップは取得できない', async () => { const clip = await create({ isPublic: false }, { user: bob } ); failedApiCall({ - endpoint: '/clips/show', + endpoint: 'clips/show', parameters: { clipId: clip.id }, user: alice, }, { @@ -323,7 +308,8 @@ describe('クリップ', () => { id: 'c3c5fe33-d62c-44d2-9ea5-d997703f5c20', } }, ])('のID指定取得は$labelならできない', async ({ parameters, assetion }) => failedApiCall({ - endpoint: '/clips/show', + endpoint: 'clips/show', + // @ts-expect-error clipId must not be undefined parameters: { ...parameters, }, @@ -356,27 +342,23 @@ describe('クリップ', () => { test('の一覧が取得できる(空)', async () => { const res = await usersClips({ - parameters: { - userId: alice.id, - }, + userId: alice.id, }); assert.deepStrictEqual(res, []); }); test.each([ { label: '' }, - { label: '他人アカウントから', user: (): User => bob }, + { label: '他人アカウントから', user: () => bob }, ])('の一覧が$label取得できる', async () => { const clips = await createMany({ isPublic: true }); const res = await usersClips({ - parameters: { - userId: alice.id, - }, + userId: alice.id, }); // 返ってくる配列には順序保障がないのでidでソートして厳密比較 assert.deepStrictEqual( - res.sort(compareBy(s => s.id)), + res.sort(compareBy(s => s.id)), clips.sort(compareBy(s => s.id))); // 認証状態で見たときだけisFavoritedが入っている @@ -386,17 +368,16 @@ describe('クリップ', () => { }); test.each([ - { label: '未認証', user: (): undefined => undefined }, + { label: '未認証', user: () => undefined }, { label: '存在しないユーザーのもの', parameters: { userId: 'xxxxxxx' } }, ])('の一覧は$labelでも取得できる', async ({ parameters, user }) => { const clips = await createMany({ isPublic: true }); const res = await usersClips({ - parameters: { - userId: alice.id, - limit: clips.length, - ...parameters, - }, - user: (user ?? ((): User => alice))(), + userId: alice.id, + limit: clips.length, + ...parameters, + }, { + user: (user ?? (() => alice))(), }); // 未認証で見たときはisFavoritedは入らない @@ -409,10 +390,8 @@ describe('クリップ', () => { await create({ isPublic: false }); const aliceClip = await create({ isPublic: true }); const res = await usersClips({ - parameters: { - userId: alice.id, - limit: 2, - }, + userId: alice.id, + limit: 2, }); assert.deepStrictEqual(res, [aliceClip]); }); @@ -421,17 +400,15 @@ describe('クリップ', () => { const clips = await createMany({ isPublic: true }, 7); clips.sort(compareBy(s => s.id)); const res = await usersClips({ - parameters: { - userId: alice.id, - sinceId: clips[1].id, - untilId: clips[5].id, - limit: 4, - }, + userId: alice.id, + sinceId: clips[1].id, + untilId: clips[5].id, + limit: 4, }); // Promise.allで返ってくる配列には順序保障がないのでidでソートして厳密比較 assert.deepStrictEqual( - res.sort(compareBy(s => s.id)), + res.sort(compareBy(s => s.id)), [clips[2], clips[3], clips[4]], // sinceIdとuntilId自体は結果に含まれない clips[1].id + ' ... ' + clips[3].id + ' with ' + clips.map(s => s.id) + ' vs. ' + res.map(s => s.id)); }); @@ -441,8 +418,9 @@ describe('クリップ', () => { { label: 'limitゼロ', parameters: { limit: 0 } }, { label: 'limit最大+1', parameters: { limit: 101 } }, ])('の一覧は$labelだと取得できない', async ({ parameters }) => failedApiCall({ - endpoint: '/users/clips', + endpoint: 'users/clips', parameters: { + // @ts-expect-error userId must not be undefined userId: alice.id, ...parameters, }, @@ -454,15 +432,15 @@ describe('クリップ', () => { })); test.each([ - { label: '作成', endpoint: '/clips/create' }, - { label: '更新', endpoint: '/clips/update' }, - { label: '削除', endpoint: '/clips/delete' }, - { label: '取得', endpoint: '/clips/list' }, - { label: 'お気に入り設定', endpoint: '/clips/favorite' }, - { label: 'お気に入り解除', endpoint: '/clips/unfavorite' }, - { label: 'お気に入り取得', endpoint: '/clips/my-favorites' }, - { label: 'ノート追加', endpoint: '/clips/add-note' }, - { label: 'ノート削除', endpoint: '/clips/remove-note' }, + { label: '作成', endpoint: 'clips/create' as const }, + { label: '更新', endpoint: 'clips/update' as const }, + { label: '削除', endpoint: 'clips/delete' as const }, + { label: '取得', endpoint: 'clips/list' as const }, + { label: 'お気に入り設定', endpoint: 'clips/favorite' as const }, + { label: 'お気に入り解除', endpoint: 'clips/unfavorite' as const }, + { label: 'お気に入り取得', endpoint: 'clips/my-favorites' as const }, + { label: 'ノート追加', endpoint: 'clips/add-note' as const }, + { label: 'ノート削除', endpoint: 'clips/remove-note' as const }, ])('の$labelは未認証ではできない', async ({ endpoint }) => await failedApiCall({ endpoint: endpoint, parameters: {}, @@ -474,35 +452,33 @@ describe('クリップ', () => { })); describe('のお気に入り', () => { - let aliceClip: Clip; + let aliceClip: Misskey.entities.Clip; - type FavoriteParam = JTDDataType; - const favorite = async (parameters: FavoriteParam, request: Partial = {}): Promise => { - return successfulApiCall({ - endpoint: '/clips/favorite', + const favorite = async (parameters: Misskey.entities.ClipsFavoriteRequest, request: Partial> = {}): Promise => { + return successfulApiCall({ + endpoint: 'clips/favorite', parameters, user: alice, ...request, }, { status: 204, - }); + }) as any as void; }; - type UnfavoriteParam = JTDDataType; - const unfavorite = async (parameters: UnfavoriteParam, request: Partial = {}): Promise => { - return successfulApiCall({ - endpoint: '/clips/unfavorite', + const unfavorite = async (parameters: Misskey.entities.ClipsUnfavoriteRequest, request: Partial> = {}): Promise => { + return successfulApiCall({ + endpoint: 'clips/unfavorite', parameters, user: alice, ...request, }, { status: 204, - }); + }) as any as void; }; - const myFavorites = async (request: Partial = {}): Promise => { - return successfulApiCall({ - endpoint: '/clips/my-favorites', + const myFavorites = async (request: Partial> = {}): Promise => { + return successfulApiCall({ + endpoint: 'clips/my-favorites', parameters: {}, user: alice, ...request, @@ -568,7 +544,7 @@ describe('クリップ', () => { test('は同じクリップに対して二回設定できない。', async () => { await favorite({ clipId: aliceClip.id }); await failedApiCall({ - endpoint: '/clips/favorite', + endpoint: 'clips/favorite', parameters: { clipId: aliceClip.id, }, @@ -586,14 +562,15 @@ describe('クリップ', () => { code: 'NO_SUCH_CLIP', id: '4c2aaeae-80d8-4250-9606-26cb1fdb77a5', } }, - { label: '他人のクリップ', user: (): User => bob, assertion: { + { label: '他人のクリップ', user: () => bob, assertion: { code: 'NO_SUCH_CLIP', id: '4c2aaeae-80d8-4250-9606-26cb1fdb77a5', } }, ])('の設定は$labelならできない', async ({ parameters, user, assertion }) => failedApiCall({ - endpoint: '/clips/favorite', + endpoint: 'clips/favorite', parameters: { - clipId: (await create({}, { user: (user ?? ((): User => alice))() })).id, + // @ts-expect-error clipId must not be null + clipId: (await create({}, { user: (user ?? (() => alice))() })).id, ...parameters, }, user: alice, @@ -619,7 +596,7 @@ describe('クリップ', () => { code: 'NO_SUCH_CLIP', id: '2603966e-b865-426c-94a7-af4a01241dc1', } }, - { label: '他人のクリップ', user: (): User => bob, assertion: { + { label: '他人のクリップ', user: () => bob, assertion: { code: 'NOT_FAVORITED', id: '90c3a9e8-b321-4dae-bf57-2bf79bbcc187', } }, @@ -628,9 +605,10 @@ describe('クリップ', () => { id: '90c3a9e8-b321-4dae-bf57-2bf79bbcc187', } }, ])('の設定解除は$labelならできない', async ({ parameters, user, assertion }) => failedApiCall({ - endpoint: '/clips/unfavorite', + endpoint: 'clips/unfavorite', parameters: { - clipId: (await create({}, { user: (user ?? ((): User => alice))() })).id, + // @ts-expect-error clipId must not be null + clipId: (await create({}, { user: (user ?? (() => alice))() })).id, ...parameters, }, user: alice, @@ -655,41 +633,38 @@ describe('クリップ', () => { }); describe('に紐づくノート', () => { - let aliceClip: Clip; + let aliceClip: Misskey.entities.Clip; - const sampleNotes = (): Note[] => [ + const sampleNotes = (): Misskey.entities.Note[] => [ aliceNote, aliceHomeNote, aliceFollowersNote, aliceSpecifiedNote, bobNote, bobHomeNote, bobFollowersNote, bobSpecifiedNote, ]; - type AddNoteParam = JTDDataType; - const addNote = async (parameters: AddNoteParam, request: Partial = {}): Promise => { - return successfulApiCall({ - endpoint: '/clips/add-note', + const addNote = async (parameters: Misskey.entities.ClipsAddNoteRequest, request: Partial> = {}): Promise => { + return successfulApiCall({ + endpoint: 'clips/add-note', parameters, user: alice, ...request, }, { status: 204, - }); + }) as any as void; }; - type RemoveNoteParam = JTDDataType; - const removeNote = async (parameters: RemoveNoteParam, request: Partial = {}): Promise => { - return successfulApiCall({ - endpoint: '/clips/remove-note', + const removeNote = async (parameters: Misskey.entities.ClipsRemoveNoteRequest, request: Partial> = {}): Promise => { + return successfulApiCall({ + endpoint: 'clips/remove-note', parameters, user: alice, ...request, }, { status: 204, - }); + }) as any as void; }; - type NotesParam = JTDDataType; - const notes = async (parameters: Partial, request: Partial = {}): Promise => { - return successfulApiCall({ - endpoint: '/clips/notes', + const notes = async (parameters: Misskey.entities.ClipsNotesRequest, request: Partial> = {}): Promise => { + return successfulApiCall({ + endpoint: 'clips/notes', parameters, user: alice, ...request, @@ -715,7 +690,7 @@ describe('クリップ', () => { test('として同じノートを二回紐づけることはできない', async () => { await addNote({ clipId: aliceClip.id, noteId: aliceNote.id }); await failedApiCall({ - endpoint: '/clips/add-note', + endpoint: 'clips/add-note', parameters: { clipId: aliceClip.id, noteId: aliceNote.id, @@ -733,11 +708,11 @@ describe('クリップ', () => { const noteLimit = DEFAULT_POLICIES.noteEachClipsLimit + 1; const noteList = await Promise.all([...Array(noteLimit)].map((_, i) => post(alice, { text: `test ${i}`, - }) as unknown)) as Note[]; + }) as unknown)) as Misskey.entities.Note[]; await Promise.all(noteList.map(s => addNote({ clipId: aliceClip.id, noteId: s.id }))); await failedApiCall({ - endpoint: '/clips/add-note', + endpoint: 'clips/add-note', parameters: { clipId: aliceClip.id, noteId: aliceNote.id, @@ -751,7 +726,7 @@ describe('クリップ', () => { }); test('は他人のクリップへ追加できない。', async () => await failedApiCall({ - endpoint: '/clips/add-note', + endpoint: 'clips/add-note', parameters: { clipId: aliceClip.id, noteId: aliceNote.id, @@ -774,18 +749,20 @@ describe('クリップ', () => { code: 'NO_SUCH_NOTE', id: 'fc8c0b49-c7a3-4664-a0a6-b418d386bb8b', } }, - { label: '他人のクリップ', user: (): object => bob, assetion: { + { label: '他人のクリップ', user: () => bob, assetion: { code: 'NO_SUCH_CLIP', id: 'd6e76cc0-a1b5-4c7c-a287-73fa9c716dcf', } }, ])('の追加は$labelだとできない', async ({ parameters, user, assetion }) => failedApiCall({ - endpoint: '/clips/add-note', + endpoint: 'clips/add-note', parameters: { + // @ts-expect-error clipId must not be undefined clipId: aliceClip.id, + // @ts-expect-error noteId must not be undefined noteId: aliceNote.id, ...parameters, }, - user: (user ?? ((): User => alice))(), + user: (user ?? (() => alice))(), }, { status: 400, code: 'INVALID_PARAM', @@ -810,18 +787,20 @@ describe('クリップ', () => { code: 'NO_SUCH_NOTE', id: 'aff017de-190e-434b-893e-33a9ff5049d8', // add-noteと異なる } }, - { label: '他人のクリップ', user: (): object => bob, assetion: { + { label: '他人のクリップ', user: () => bob, assetion: { code: 'NO_SUCH_CLIP', id: 'b80525c6-97f7-49d7-a42d-ebccd49cfd52', // add-noteと異なる } }, ])('の削除は$labelだとできない', async ({ parameters, user, assetion }) => failedApiCall({ - endpoint: '/clips/remove-note', + endpoint: 'clips/remove-note', parameters: { + // @ts-expect-error clipId must not be undefined clipId: aliceClip.id, + // @ts-expect-error noteId must not be undefined noteId: aliceNote.id, ...parameters, }, - user: (user ?? ((): User => alice))(), + user: (user ?? (() => alice))(), }, { status: 400, code: 'INVALID_PARAM', @@ -925,21 +904,22 @@ describe('クリップ', () => { code: 'NO_SUCH_CLIP', id: '1d7645e6-2b6d-4635-b0fe-fe22b0e72e00', } }, - { label: '他人のPrivateなクリップから', user: (): object => bob, assertion: { + { label: '他人のPrivateなクリップから', user: () => bob, assertion: { code: 'NO_SUCH_CLIP', id: '1d7645e6-2b6d-4635-b0fe-fe22b0e72e00', } }, - { label: '未認証でPrivateなクリップから', user: (): undefined => undefined, assertion: { + { label: '未認証でPrivateなクリップから', user: () => undefined, assertion: { code: 'NO_SUCH_CLIP', id: '1d7645e6-2b6d-4635-b0fe-fe22b0e72e00', } }, ])('は$labelだと取得できない', async ({ parameters, user, assertion }) => failedApiCall({ - endpoint: '/clips/notes', + endpoint: 'clips/notes', parameters: { + // @ts-expect-error clipId must not be undefined clipId: aliceClip.id, ...parameters, }, - user: (user ?? ((): User => alice))(), + user: (user ?? (() => alice))(), }, { status: 400, code: 'INVALID_PARAM', diff --git a/packages/backend/test/e2e/drive.ts b/packages/backend/test/e2e/drive.ts index 22ec66e2afcb..828c5200ef0d 100644 --- a/packages/backend/test/e2e/drive.ts +++ b/packages/backend/test/e2e/drive.ts @@ -6,22 +6,14 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; -import { MiNote } from '@/models/Note.js'; -import { api, initTestDb, makeStreamCatcher, post, signup, uploadFile } from '../utils.js'; +import { api, makeStreamCatcher, post, signup, uploadFile } from '../utils.js'; import type * as misskey from 'misskey-js'; -import type{ Repository } from 'typeorm' -import type { Packed } from '@/misc/json-schema.js'; - describe('Drive', () => { - let Notes: Repository; - let alice: misskey.entities.SignupResponse; let bob: misskey.entities.SignupResponse; beforeAll(async () => { - const connection = await initTestDb(true); - Notes = connection.getRepository(MiNote); alice = await signup({ username: 'alice' }); bob = await signup({ username: 'bob' }); }, 1000 * 60 * 2); @@ -31,13 +23,13 @@ describe('Drive', () => { const marker = Math.random().toString(); - const url = 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg' + const url = 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg'; const catcher = makeStreamCatcher( alice, 'main', (msg) => msg.type === 'urlUploadFinished' && msg.body.marker === marker, - (msg) => msg.body.file as Packed<'DriveFile'>, + (msg) => msg.body.file, 10 * 1000); const res = await api('drive/files/upload-from-url', { @@ -51,7 +43,7 @@ describe('Drive', () => { assert.strictEqual(res.status, 204); assert.strictEqual(file.name, 'Lenna.jpg'); assert.strictEqual(file.type, 'image/jpeg'); - }) + }); test('ローカルからアップロードできる', async () => { // APIレスポンスを直接使用するので utils.js uploadFile が通過することで成功とする @@ -59,27 +51,27 @@ describe('Drive', () => { const res = await uploadFile(alice, { path: 'Lenna.jpg', name: 'テスト画像' }); assert.strictEqual(res.body?.name, 'テスト画像.jpg'); - assert.strictEqual(res.body?.type, 'image/jpeg'); - }) + assert.strictEqual(res.body.type, 'image/jpeg'); + }); test('添付ノート一覧を取得できる', async () => { - const ids = (await Promise.all([uploadFile(alice), uploadFile(alice), uploadFile(alice)])).map(elm => elm.body!.id) + const ids = (await Promise.all([uploadFile(alice), uploadFile(alice), uploadFile(alice)])).map(elm => elm.body!.id); const note0 = await post(alice, { fileIds: [ids[0]] }); const note1 = await post(alice, { fileIds: [ids[0], ids[1]] }); const attached0 = await api('drive/files/attached-notes', { fileId: ids[0] }, alice); assert.strictEqual(attached0.body.length, 2); - assert.strictEqual(attached0.body[0].id, note1.id) - assert.strictEqual(attached0.body[1].id, note0.id) + assert.strictEqual(attached0.body[0].id, note1.id); + assert.strictEqual(attached0.body[1].id, note0.id); const attached1 = await api('drive/files/attached-notes', { fileId: ids[1] }, alice); assert.strictEqual(attached1.body.length, 1); - assert.strictEqual(attached1.body[0].id, note1.id) + assert.strictEqual(attached1.body[0].id, note1.id); const attached2 = await api('drive/files/attached-notes', { fileId: ids[2] }, alice); - assert.strictEqual(attached2.body.length, 0) - }) + assert.strictEqual(attached2.body.length, 0); + }); test('添付ノート一覧は他の人から見えない', async () => { const file = await uploadFile(alice); @@ -89,7 +81,5 @@ describe('Drive', () => { const res = await api('drive/files/attached-notes', { fileId: file.body!.id }, bob); assert.strictEqual(res.status, 400); assert.strictEqual('error' in res.body, true); - - }) + }); }); - diff --git a/packages/backend/test/e2e/endpoints.ts b/packages/backend/test/e2e/endpoints.ts index d4695978053e..bc89dc37f4d8 100644 --- a/packages/backend/test/e2e/endpoints.ts +++ b/packages/backend/test/e2e/endpoints.ts @@ -79,6 +79,7 @@ describe('Endpoints', () => { test('クエリをインジェクションできない', async () => { const res = await api('signin', { username: 'test1', + // @ts-expect-error password must be string password: { $gt: '', }, @@ -103,7 +104,7 @@ describe('Endpoints', () => { const myLocation = '七森中'; const myBirthday = '2000-09-07'; - const res = await api('/i/update', { + const res = await api('i/update', { name: myName, location: myLocation, birthday: myBirthday, @@ -117,7 +118,7 @@ describe('Endpoints', () => { }); test('名前を空白にできる', async () => { - const res = await api('/i/update', { + const res = await api('i/update', { name: ' ', }, alice); assert.strictEqual(res.status, 200); @@ -125,11 +126,11 @@ describe('Endpoints', () => { }); test('誕生日の設定を削除できる', async () => { - await api('/i/update', { + await api('i/update', { birthday: '2000-09-07', }, alice); - const res = await api('/i/update', { + const res = await api('i/update', { birthday: null, }, alice); @@ -139,7 +140,7 @@ describe('Endpoints', () => { }); test('不正な誕生日の形式で怒られる', async () => { - const res = await api('/i/update', { + const res = await api('i/update', { birthday: '2000/09/07', }, alice); assert.strictEqual(res.status, 400); @@ -148,7 +149,7 @@ describe('Endpoints', () => { describe('users/show', () => { test('ユーザーが取得できる', async () => { - const res = await api('/users/show', { + const res = await api('users/show', { userId: alice.id, }, alice); @@ -158,14 +159,14 @@ describe('Endpoints', () => { }); test('ユーザーが存在しなかったら怒る', async () => { - const res = await api('/users/show', { + const res = await api('users/show', { userId: '000000000000000000000000', }); assert.strictEqual(res.status, 404); }); test('間違ったIDで怒られる', async () => { - const res = await api('/users/show', { + const res = await api('users/show', { userId: 'kyoppie', }); assert.strictEqual(res.status, 404); @@ -178,7 +179,7 @@ describe('Endpoints', () => { text: 'test', }); - const res = await api('/notes/show', { + const res = await api('notes/show', { noteId: myPost.id, }, alice); @@ -189,14 +190,14 @@ describe('Endpoints', () => { }); test('投稿が存在しなかったら怒る', async () => { - const res = await api('/notes/show', { + const res = await api('notes/show', { noteId: '000000000000000000000000', }); assert.strictEqual(res.status, 400); }); test('間違ったIDで怒られる', async () => { - const res = await api('/notes/show', { + const res = await api('notes/show', { noteId: 'kyoppie', }); assert.strictEqual(res.status, 400); @@ -207,14 +208,14 @@ describe('Endpoints', () => { test('リアクションできる', async () => { const bobPost = await post(bob, { text: 'hi' }); - const res = await api('/notes/reactions/create', { + const res = await api('notes/reactions/create', { noteId: bobPost.id, reaction: '🚀', }, alice); assert.strictEqual(res.status, 204); - const resNote = await api('/notes/show', { + const resNote = await api('notes/show', { noteId: bobPost.id, }, alice); @@ -225,7 +226,7 @@ describe('Endpoints', () => { test('自分の投稿にもリアクションできる', async () => { const myPost = await post(alice, { text: 'hi' }); - const res = await api('/notes/reactions/create', { + const res = await api('notes/reactions/create', { noteId: myPost.id, reaction: '🚀', }, alice); @@ -236,19 +237,19 @@ describe('Endpoints', () => { test('二重にリアクションすると上書きされる', async () => { const bobPost = await post(bob, { text: 'hi' }); - await api('/notes/reactions/create', { + await api('notes/reactions/create', { noteId: bobPost.id, reaction: '🥰', }, alice); - const res = await api('/notes/reactions/create', { + const res = await api('notes/reactions/create', { noteId: bobPost.id, reaction: '🚀', }, alice); assert.strictEqual(res.status, 204); - const resNote = await api('/notes/show', { + const resNote = await api('notes/show', { noteId: bobPost.id, }, alice); @@ -257,7 +258,7 @@ describe('Endpoints', () => { }); test('存在しない投稿にはリアクションできない', async () => { - const res = await api('/notes/reactions/create', { + const res = await api('notes/reactions/create', { noteId: '000000000000000000000000', reaction: '🚀', }, alice); @@ -266,13 +267,14 @@ describe('Endpoints', () => { }); test('空のパラメータで怒られる', async () => { - const res = await api('/notes/reactions/create', {}, alice); + // @ts-expect-error param must not be empty + const res = await api('notes/reactions/create', {}, alice); assert.strictEqual(res.status, 400); }); test('間違ったIDで怒られる', async () => { - const res = await api('/notes/reactions/create', { + const res = await api('notes/reactions/create', { noteId: 'kyoppie', reaction: '🚀', }, alice); @@ -283,7 +285,7 @@ describe('Endpoints', () => { describe('following/create', () => { test('フォローできる', async () => { - const res = await api('/following/create', { + const res = await api('following/create', { userId: alice.id, }, bob); @@ -301,7 +303,7 @@ describe('Endpoints', () => { }); test('既にフォローしている場合は怒る', async () => { - const res = await api('/following/create', { + const res = await api('following/create', { userId: alice.id, }, bob); @@ -309,7 +311,7 @@ describe('Endpoints', () => { }); test('存在しないユーザーはフォローできない', async () => { - const res = await api('/following/create', { + const res = await api('following/create', { userId: '000000000000000000000000', }, alice); @@ -317,7 +319,7 @@ describe('Endpoints', () => { }); test('自分自身はフォローできない', async () => { - const res = await api('/following/create', { + const res = await api('following/create', { userId: alice.id, }, alice); @@ -325,13 +327,14 @@ describe('Endpoints', () => { }); test('空のパラメータで怒られる', async () => { - const res = await api('/following/create', {}, alice); + // @ts-expect-error params must not be empty + const res = await api('following/create', {}, alice); assert.strictEqual(res.status, 400); }); test('間違ったIDで怒られる', async () => { - const res = await api('/following/create', { + const res = await api('following/create', { userId: 'foo', }, alice); @@ -341,11 +344,11 @@ describe('Endpoints', () => { describe('following/delete', () => { test('フォロー解除できる', async () => { - await api('/following/create', { + await api('following/create', { userId: alice.id, }, bob); - const res = await api('/following/delete', { + const res = await api('following/delete', { userId: alice.id, }, bob); @@ -363,7 +366,7 @@ describe('Endpoints', () => { }); test('フォローしていない場合は怒る', async () => { - const res = await api('/following/delete', { + const res = await api('following/delete', { userId: alice.id, }, bob); @@ -371,7 +374,7 @@ describe('Endpoints', () => { }); test('存在しないユーザーはフォロー解除できない', async () => { - const res = await api('/following/delete', { + const res = await api('following/delete', { userId: '000000000000000000000000', }, alice); @@ -379,7 +382,7 @@ describe('Endpoints', () => { }); test('自分自身はフォロー解除できない', async () => { - const res = await api('/following/delete', { + const res = await api('following/delete', { userId: alice.id, }, alice); @@ -387,13 +390,14 @@ describe('Endpoints', () => { }); test('空のパラメータで怒られる', async () => { - const res = await api('/following/delete', {}, alice); + // @ts-expect-error params must not be empty + const res = await api('following/delete', {}, alice); assert.strictEqual(res.status, 400); }); test('間違ったIDで怒られる', async () => { - const res = await api('/following/delete', { + const res = await api('following/delete', { userId: 'kyoppie', }, alice); @@ -403,20 +407,20 @@ describe('Endpoints', () => { describe('channels/search', () => { test('空白検索で一覧を取得できる', async () => { - await api('/channels/create', { + await api('channels/create', { name: 'aaa', description: 'bbb', }, bob); - await api('/channels/create', { + await api('channels/create', { name: 'ccc1', description: 'ddd1', }, bob); - await api('/channels/create', { + await api('channels/create', { name: 'ccc2', description: 'ddd2', }, bob); - const res = await api('/channels/search', { + const res = await api('channels/search', { query: '', }, bob); @@ -425,7 +429,7 @@ describe('Endpoints', () => { assert.strictEqual(res.body.length, 3); }); test('名前のみの検索で名前を検索できる', async () => { - const res = await api('/channels/search', { + const res = await api('channels/search', { query: 'aaa', type: 'nameOnly', }, bob); @@ -436,7 +440,7 @@ describe('Endpoints', () => { assert.strictEqual(res.body[0].name, 'aaa'); }); test('名前のみの検索で名前を複数検索できる', async () => { - const res = await api('/channels/search', { + const res = await api('channels/search', { query: 'ccc', type: 'nameOnly', }, bob); @@ -446,7 +450,7 @@ describe('Endpoints', () => { assert.strictEqual(res.body.length, 2); }); test('名前のみの検索で説明は検索できない', async () => { - const res = await api('/channels/search', { + const res = await api('channels/search', { query: 'bbb', type: 'nameOnly', }, bob); @@ -456,7 +460,7 @@ describe('Endpoints', () => { assert.strictEqual(res.body.length, 0); }); test('名前と説明の検索で名前を検索できる', async () => { - const res = await api('/channels/search', { + const res = await api('channels/search', { query: 'ccc1', }, bob); @@ -466,7 +470,7 @@ describe('Endpoints', () => { assert.strictEqual(res.body[0].name, 'ccc1'); }); test('名前と説明での検索で説明を検索できる', async () => { - const res = await api('/channels/search', { + const res = await api('channels/search', { query: 'ddd1', }, bob); @@ -476,7 +480,7 @@ describe('Endpoints', () => { assert.strictEqual(res.body[0].name, 'ccc1'); }); test('名前と説明の検索で名前を複数検索できる', async () => { - const res = await api('/channels/search', { + const res = await api('channels/search', { query: 'ccc', }, bob); @@ -485,7 +489,7 @@ describe('Endpoints', () => { assert.strictEqual(res.body.length, 2); }); test('名前と説明での検索で説明を複数検索できる', async () => { - const res = await api('/channels/search', { + const res = await api('channels/search', { query: 'ddd', }, bob); @@ -506,7 +510,7 @@ describe('Endpoints', () => { await uploadFile(alice, { blob: new Blob([new Uint8Array(1024)]), }); - const res = await api('/drive', {}, alice); + const res = await api('drive', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true); expect(res.body).toHaveProperty('usage', 1792); @@ -519,7 +523,7 @@ describe('Endpoints', () => { assert.strictEqual(res.status, 200); assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true); - assert.strictEqual(res.body.name, 'Lenna.jpg'); + assert.strictEqual(res.body!.name, 'Lenna.jpg'); }); test('ファイルに名前を付けられる', async () => { @@ -527,7 +531,7 @@ describe('Endpoints', () => { assert.strictEqual(res.status, 200); assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true); - assert.strictEqual(res.body.name, 'Belmond.jpg'); + assert.strictEqual(res.body!.name, 'Belmond.jpg'); }); test('ファイルに名前を付けられるが、拡張子は正しいものになる', async () => { @@ -535,11 +539,12 @@ describe('Endpoints', () => { assert.strictEqual(res.status, 200); assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true); - assert.strictEqual(res.body.name, 'Belmond.png.jpg'); + assert.strictEqual(res.body!.name, 'Belmond.png.jpg'); }); test('ファイル無しで怒られる', async () => { - const res = await api('/drive/files/create', {}, alice); + // @ts-expect-error params must not be empty + const res = await api('drive/files/create', {}, alice); assert.strictEqual(res.status, 400); }); @@ -549,14 +554,14 @@ describe('Endpoints', () => { assert.strictEqual(res.status, 200); assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true); - assert.strictEqual(res.body.name, 'image.svg'); - assert.strictEqual(res.body.type, 'image/svg+xml'); + assert.strictEqual(res.body!.name, 'image.svg'); + assert.strictEqual(res.body!.type, 'image/svg+xml'); }); for (const type of ['webp', 'avif']) { const mediaType = `image/${type}`; - const getWebpublicType = async (user: any, fileId: string): Promise => { + const getWebpublicType = async (user: misskey.entities.SignupResponse, fileId: string): Promise => { // drive/files/create does not expose webpublicType directly, so get it by posting it const res = await post(user, { text: mediaType, @@ -573,10 +578,10 @@ describe('Endpoints', () => { const res = await uploadFile(alice, { path }); assert.strictEqual(res.status, 200); - assert.strictEqual(res.body.name, path); - assert.strictEqual(res.body.type, mediaType); + assert.strictEqual(res.body!.name, path); + assert.strictEqual(res.body!.type, mediaType); - const webpublicType = await getWebpublicType(alice, res.body.id); + const webpublicType = await getWebpublicType(alice, res.body!.id); assert.strictEqual(webpublicType, 'image/webp'); }); @@ -584,10 +589,10 @@ describe('Endpoints', () => { const path = `without-alpha.${type}`; const res = await uploadFile(alice, { path }); assert.strictEqual(res.status, 200); - assert.strictEqual(res.body.name, path); - assert.strictEqual(res.body.type, mediaType); + assert.strictEqual(res.body!.name, path); + assert.strictEqual(res.body!.type, mediaType); - const webpublicType = await getWebpublicType(alice, res.body.id); + const webpublicType = await getWebpublicType(alice, res.body!.id); assert.strictEqual(webpublicType, 'image/webp'); }); } @@ -598,8 +603,8 @@ describe('Endpoints', () => { const file = (await uploadFile(alice)).body; const newName = 'いちごパスタ.png'; - const res = await api('/drive/files/update', { - fileId: file.id, + const res = await api('drive/files/update', { + fileId: file!.id, name: newName, }, alice); @@ -611,8 +616,8 @@ describe('Endpoints', () => { test('他人のファイルは更新できない', async () => { const file = (await uploadFile(alice)).body; - const res = await api('/drive/files/update', { - fileId: file.id, + const res = await api('drive/files/update', { + fileId: file!.id, name: 'いちごパスタ.png', }, bob); @@ -621,12 +626,12 @@ describe('Endpoints', () => { test('親フォルダを更新できる', async () => { const file = (await uploadFile(alice)).body; - const folder = (await api('/drive/folders/create', { + const folder = (await api('drive/folders/create', { name: 'test', }, alice)).body; - const res = await api('/drive/files/update', { - fileId: file.id, + const res = await api('drive/files/update', { + fileId: file!.id, folderId: folder.id, }, alice); @@ -638,17 +643,17 @@ describe('Endpoints', () => { test('親フォルダを無しにできる', async () => { const file = (await uploadFile(alice)).body; - const folder = (await api('/drive/folders/create', { + const folder = (await api('drive/folders/create', { name: 'test', }, alice)).body; - await api('/drive/files/update', { - fileId: file.id, + await api('drive/files/update', { + fileId: file!.id, folderId: folder.id, }, alice); - const res = await api('/drive/files/update', { - fileId: file.id, + const res = await api('drive/files/update', { + fileId: file!.id, folderId: null, }, alice); @@ -659,12 +664,12 @@ describe('Endpoints', () => { test('他人のフォルダには入れられない', async () => { const file = (await uploadFile(alice)).body; - const folder = (await api('/drive/folders/create', { + const folder = (await api('drive/folders/create', { name: 'test', }, bob)).body; - const res = await api('/drive/files/update', { - fileId: file.id, + const res = await api('drive/files/update', { + fileId: file!.id, folderId: folder.id, }, alice); @@ -674,8 +679,8 @@ describe('Endpoints', () => { test('存在しないフォルダで怒られる', async () => { const file = (await uploadFile(alice)).body; - const res = await api('/drive/files/update', { - fileId: file.id, + const res = await api('drive/files/update', { + fileId: file!.id, folderId: '000000000000000000000000', }, alice); @@ -685,8 +690,8 @@ describe('Endpoints', () => { test('不正なフォルダIDで怒られる', async () => { const file = (await uploadFile(alice)).body; - const res = await api('/drive/files/update', { - fileId: file.id, + const res = await api('drive/files/update', { + fileId: file!.id, folderId: 'foo', }, alice); @@ -694,7 +699,7 @@ describe('Endpoints', () => { }); test('ファイルが存在しなかったら怒る', async () => { - const res = await api('/drive/files/update', { + const res = await api('drive/files/update', { fileId: '000000000000000000000000', name: 'いちごパスタ.png', }, alice); @@ -706,8 +711,8 @@ describe('Endpoints', () => { const file = (await uploadFile(alice)).body; const newName = ''; - const res = await api('/drive/files/update', { - fileId: file.id, + const res = await api('drive/files/update', { + fileId: file!.id, name: newName, }, alice); @@ -715,7 +720,7 @@ describe('Endpoints', () => { }); test('間違ったIDで怒られる', async () => { - const res = await api('/drive/files/update', { + const res = await api('drive/files/update', { fileId: 'kyoppie', name: 'いちごパスタ.png', }, alice); @@ -726,7 +731,7 @@ describe('Endpoints', () => { describe('drive/folders/create', () => { test('フォルダを作成できる', async () => { - const res = await api('/drive/folders/create', { + const res = await api('drive/folders/create', { name: 'test', }, alice); @@ -738,11 +743,11 @@ describe('Endpoints', () => { describe('drive/folders/update', () => { test('名前を更新できる', async () => { - const folder = (await api('/drive/folders/create', { + const folder = (await api('drive/folders/create', { name: 'test', }, alice)).body; - const res = await api('/drive/folders/update', { + const res = await api('drive/folders/update', { folderId: folder.id, name: 'new name', }, alice); @@ -753,11 +758,11 @@ describe('Endpoints', () => { }); test('他人のフォルダを更新できない', async () => { - const folder = (await api('/drive/folders/create', { + const folder = (await api('drive/folders/create', { name: 'test', }, bob)).body; - const res = await api('/drive/folders/update', { + const res = await api('drive/folders/update', { folderId: folder.id, name: 'new name', }, alice); @@ -766,14 +771,14 @@ describe('Endpoints', () => { }); test('親フォルダを更新できる', async () => { - const folder = (await api('/drive/folders/create', { + const folder = (await api('drive/folders/create', { name: 'test', }, alice)).body; - const parentFolder = (await api('/drive/folders/create', { + const parentFolder = (await api('drive/folders/create', { name: 'parent', }, alice)).body; - const res = await api('/drive/folders/update', { + const res = await api('drive/folders/update', { folderId: folder.id, parentId: parentFolder.id, }, alice); @@ -784,18 +789,18 @@ describe('Endpoints', () => { }); test('親フォルダを無しに更新できる', async () => { - const folder = (await api('/drive/folders/create', { + const folder = (await api('drive/folders/create', { name: 'test', }, alice)).body; - const parentFolder = (await api('/drive/folders/create', { + const parentFolder = (await api('drive/folders/create', { name: 'parent', }, alice)).body; - await api('/drive/folders/update', { + await api('drive/folders/update', { folderId: folder.id, parentId: parentFolder.id, }, alice); - const res = await api('/drive/folders/update', { + const res = await api('drive/folders/update', { folderId: folder.id, parentId: null, }, alice); @@ -806,14 +811,14 @@ describe('Endpoints', () => { }); test('他人のフォルダを親フォルダに設定できない', async () => { - const folder = (await api('/drive/folders/create', { + const folder = (await api('drive/folders/create', { name: 'test', }, alice)).body; - const parentFolder = (await api('/drive/folders/create', { + const parentFolder = (await api('drive/folders/create', { name: 'parent', }, bob)).body; - const res = await api('/drive/folders/update', { + const res = await api('drive/folders/update', { folderId: folder.id, parentId: parentFolder.id, }, alice); @@ -822,18 +827,18 @@ describe('Endpoints', () => { }); test('フォルダが循環するような構造にできない', async () => { - const folder = (await api('/drive/folders/create', { + const folder = (await api('drive/folders/create', { name: 'test', }, alice)).body; - const parentFolder = (await api('/drive/folders/create', { + const parentFolder = (await api('drive/folders/create', { name: 'parent', }, alice)).body; - await api('/drive/folders/update', { + await api('drive/folders/update', { folderId: parentFolder.id, parentId: folder.id, }, alice); - const res = await api('/drive/folders/update', { + const res = await api('drive/folders/update', { folderId: folder.id, parentId: parentFolder.id, }, alice); @@ -842,25 +847,25 @@ describe('Endpoints', () => { }); test('フォルダが循環するような構造にできない(再帰的)', async () => { - const folderA = (await api('/drive/folders/create', { + const folderA = (await api('drive/folders/create', { name: 'test', }, alice)).body; - const folderB = (await api('/drive/folders/create', { + const folderB = (await api('drive/folders/create', { name: 'test', }, alice)).body; - const folderC = (await api('/drive/folders/create', { + const folderC = (await api('drive/folders/create', { name: 'test', }, alice)).body; - await api('/drive/folders/update', { + await api('drive/folders/update', { folderId: folderB.id, parentId: folderA.id, }, alice); - await api('/drive/folders/update', { + await api('drive/folders/update', { folderId: folderC.id, parentId: folderB.id, }, alice); - const res = await api('/drive/folders/update', { + const res = await api('drive/folders/update', { folderId: folderA.id, parentId: folderC.id, }, alice); @@ -869,11 +874,11 @@ describe('Endpoints', () => { }); test('フォルダが循環するような構造にできない(自身)', async () => { - const folderA = (await api('/drive/folders/create', { + const folderA = (await api('drive/folders/create', { name: 'test', }, alice)).body; - const res = await api('/drive/folders/update', { + const res = await api('drive/folders/update', { folderId: folderA.id, parentId: folderA.id, }, alice); @@ -882,11 +887,11 @@ describe('Endpoints', () => { }); test('存在しない親フォルダを設定できない', async () => { - const folder = (await api('/drive/folders/create', { + const folder = (await api('drive/folders/create', { name: 'test', }, alice)).body; - const res = await api('/drive/folders/update', { + const res = await api('drive/folders/update', { folderId: folder.id, parentId: '000000000000000000000000', }, alice); @@ -895,11 +900,11 @@ describe('Endpoints', () => { }); test('不正な親フォルダIDで怒られる', async () => { - const folder = (await api('/drive/folders/create', { + const folder = (await api('drive/folders/create', { name: 'test', }, alice)).body; - const res = await api('/drive/folders/update', { + const res = await api('drive/folders/update', { folderId: folder.id, parentId: 'foo', }, alice); @@ -908,7 +913,7 @@ describe('Endpoints', () => { }); test('存在しないフォルダを更新できない', async () => { - const res = await api('/drive/folders/update', { + const res = await api('drive/folders/update', { folderId: '000000000000000000000000', }, alice); @@ -916,7 +921,7 @@ describe('Endpoints', () => { }); test('不正なフォルダIDで怒られる', async () => { - const res = await api('/drive/folders/update', { + const res = await api('drive/folders/update', { folderId: 'foo', }, alice); @@ -937,7 +942,7 @@ describe('Endpoints', () => { visibleUserIds: [alice.id], }); - const res = await api('/notes/replies', { + const res = await api('notes/replies', { noteId: alicePost.id, }, carol); @@ -949,7 +954,7 @@ describe('Endpoints', () => { describe('notes/timeline', () => { test('フォロワー限定投稿が含まれる', async () => { - await api('/following/create', { + await api('following/create', { userId: carol.id, }, dave); @@ -958,7 +963,7 @@ describe('Endpoints', () => { visibility: 'followers', }); - const res = await api('/notes/timeline', {}, dave); + const res = await api('notes/timeline', {}, dave); assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); @@ -979,12 +984,12 @@ describe('Endpoints', () => { test('他者に関するメモを更新できる', async () => { const memo = '10月まで低浮上とのこと。'; - const res1 = await api('/users/update-memo', { + const res1 = await api('users/update-memo', { memo, userId: bob.id, }, alice); - const res2 = await api('/users/show', { + const res2 = await api('users/show', { userId: bob.id, }, alice); assert.strictEqual(res1.status, 204); @@ -994,12 +999,12 @@ describe('Endpoints', () => { test('自分に関するメモを更新できる', async () => { const memo = 'チケットを月末までに買う。'; - const res1 = await api('/users/update-memo', { + const res1 = await api('users/update-memo', { memo, userId: alice.id, }, alice); - const res2 = await api('/users/show', { + const res2 = await api('users/show', { userId: alice.id, }, alice); assert.strictEqual(res1.status, 204); @@ -1009,17 +1014,17 @@ describe('Endpoints', () => { test('メモを削除できる', async () => { const memo = '10月まで低浮上とのこと。'; - await api('/users/update-memo', { + await api('users/update-memo', { memo, userId: bob.id, }, alice); - await api('/users/update-memo', { + await api('users/update-memo', { memo: '', userId: bob.id, }, alice); - const res = await api('/users/show', { + const res = await api('users/show', { userId: bob.id, }, alice); @@ -1032,21 +1037,21 @@ describe('Endpoints', () => { const memoCarolToBob = '例の件について今度問いただす。'; await Promise.all([ - api('/users/update-memo', { + api('users/update-memo', { memo: memoAliceToBob, userId: bob.id, }, alice), - api('/users/update-memo', { + api('users/update-memo', { memo: memoCarolToBob, userId: bob.id, }, carol), ]); const [resAlice, resCarol] = await Promise.all([ - api('/users/show', { + api('users/show', { userId: bob.id, }, alice), - api('/users/show', { + api('users/show', { userId: bob.id, }, carol), ]); diff --git a/packages/backend/test/e2e/exports.ts b/packages/backend/test/e2e/exports.ts index eb03935a2ad6..80a5331a6dce 100644 --- a/packages/backend/test/e2e/exports.ts +++ b/packages/backend/test/e2e/exports.ts @@ -18,7 +18,7 @@ describe('export-clips', () => { // XXX: Any better way to get the result? async function pollFirstDriveFile() { while (true) { - const files = (await api('/drive/files', {}, alice)).body; + const files = (await api('drive/files', {}, alice)).body; if (!files.length) { await new Promise(r => setTimeout(r, 100)); continue; @@ -26,7 +26,7 @@ describe('export-clips', () => { if (files.length > 1) { throw new Error('Too many files?'); } - const file = (await api('/drive/files/show', { fileId: files[0].id }, alice)).body; + const file = (await api('drive/files/show', { fileId: files[0].id }, alice)).body; const res = await fetch(new URL(new URL(file.url).pathname, `http://127.0.0.1:${port}`)); return await res.json(); } @@ -44,16 +44,16 @@ describe('export-clips', () => { beforeEach(async () => { // Clean all clips and files of alice - const clips = (await api('/clips/list', {}, alice)).body; + const clips = (await api('clips/list', {}, alice)).body; for (const clip of clips) { - const res = await api('/clips/delete', { clipId: clip.id }, alice); + const res = await api('clips/delete', { clipId: clip.id }, alice); if (res.status !== 204) { throw new Error('Failed to delete clip'); } } - const files = (await api('/drive/files', {}, alice)).body; + const files = (await api('drive/files', {}, alice)).body; for (const file of files) { - const res = await api('/drive/files/delete', { fileId: file.id }, alice); + const res = await api('drive/files/delete', { fileId: file.id }, alice); if (res.status !== 204) { throw new Error('Failed to delete file'); } @@ -61,13 +61,13 @@ describe('export-clips', () => { }); test('basic export', async () => { - let res = await api('/clips/create', { + let res = await api('clips/create', { name: 'foo', description: 'bar', }, alice); assert.strictEqual(res.status, 200); - res = await api('/i/export-clips', {}, alice); + res = await api('i/export-clips', {}, alice); assert.strictEqual(res.status, 204); const exported = await pollFirstDriveFile(); @@ -77,7 +77,7 @@ describe('export-clips', () => { }); test('export with notes', async () => { - let res = await api('/clips/create', { + let res = await api('clips/create', { name: 'foo', description: 'bar', }, alice); @@ -96,14 +96,14 @@ describe('export-clips', () => { }); for (const note of [note1, note2]) { - res = await api('/clips/add-note', { + res = await api('clips/add-note', { clipId: clip.id, noteId: note.id, }, alice); assert.strictEqual(res.status, 204); } - res = await api('/i/export-clips', {}, alice); + res = await api('i/export-clips', {}, alice); assert.strictEqual(res.status, 204); const exported = await pollFirstDriveFile(); @@ -116,14 +116,14 @@ describe('export-clips', () => { }); test('multiple clips', async () => { - let res = await api('/clips/create', { + let res = await api('clips/create', { name: 'kawaii', description: 'kawaii', }, alice); assert.strictEqual(res.status, 200); const clip1 = res.body; - res = await api('/clips/create', { + res = await api('clips/create', { name: 'yuri', description: 'yuri', }, alice); @@ -138,19 +138,19 @@ describe('export-clips', () => { text: 'baz2', }); - res = await api('/clips/add-note', { + res = await api('clips/add-note', { clipId: clip1.id, noteId: note1.id, }, alice); assert.strictEqual(res.status, 204); - res = await api('/clips/add-note', { + res = await api('clips/add-note', { clipId: clip2.id, noteId: note2.id, }, alice); assert.strictEqual(res.status, 204); - res = await api('/i/export-clips', {}, alice); + res = await api('i/export-clips', {}, alice); assert.strictEqual(res.status, 204); const exported = await pollFirstDriveFile(); @@ -163,7 +163,7 @@ describe('export-clips', () => { }); test('Clipping other user\'s note', async () => { - let res = await api('/clips/create', { + let res = await api('clips/create', { name: 'kawaii', description: 'kawaii', }, alice); @@ -175,13 +175,13 @@ describe('export-clips', () => { visibility: 'followers', }); - res = await api('/clips/add-note', { + res = await api('clips/add-note', { clipId: clip.id, noteId: note.id, }, alice); assert.strictEqual(res.status, 204); - res = await api('/i/export-clips', {}, alice); + res = await api('i/export-clips', {}, alice); assert.strictEqual(res.status, 204); const exported = await pollFirstDriveFile(); diff --git a/packages/backend/test/e2e/fetch-resource.ts b/packages/backend/test/e2e/fetch-resource.ts index 74033b7dfff2..4851ed14be6a 100644 --- a/packages/backend/test/e2e/fetch-resource.ts +++ b/packages/backend/test/e2e/fetch-resource.ts @@ -23,13 +23,13 @@ const JSON_UTF8 = 'application/json; charset=utf-8'; describe('Webリソース', () => { let alice: misskey.entities.SignupResponse; - let aliceUploadedFile: any; - let alicesPost: any; - let alicePage: any; - let alicePlay: any; - let aliceClip: any; - let aliceGalleryPost: any; - let aliceChannel: any; + let aliceUploadedFile: misskey.entities.DriveFile | null; + let alicesPost: misskey.entities.Note; + let alicePage: misskey.entities.Page; + let alicePlay: misskey.entities.Flash; + let aliceClip: misskey.entities.Clip; + let aliceGalleryPost: misskey.entities.GalleryPost; + let aliceChannel: misskey.entities.Channel; let bob: misskey.entities.SignupResponse; @@ -77,7 +77,7 @@ describe('Webリソース', () => { beforeAll(async () => { alice = await signup({ username: 'alice' }); - aliceUploadedFile = await uploadFile(alice); + aliceUploadedFile = (await uploadFile(alice)).body; alicesPost = await post(alice, { text: 'test', }); @@ -85,7 +85,7 @@ describe('Webリソース', () => { alicePlay = await play(alice, {}); aliceClip = await clip(alice, {}); aliceGalleryPost = await galleryPost(alice, { - fileIds: [aliceUploadedFile.body.id], + fileIds: [aliceUploadedFile!.id], }); aliceChannel = await channel(alice, {}); diff --git a/packages/backend/test/e2e/ff-visibility.ts b/packages/backend/test/e2e/ff-visibility.ts index b59dd8824ab1..5d0c70a3c24e 100644 --- a/packages/backend/test/e2e/ff-visibility.ts +++ b/packages/backend/test/e2e/ff-visibility.ts @@ -19,15 +19,15 @@ describe('FF visibility', () => { }, 1000 * 60 * 2); test('followingVisibility, followersVisibility がともに public なユーザーのフォロー/フォロワーを誰でも見れる', async () => { - await api('/i/update', { + await api('i/update', { followingVisibility: 'public', followersVisibility: 'public', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, bob); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, bob); @@ -39,36 +39,36 @@ describe('FF visibility', () => { test('followingVisibility が public であれば followersVisibility の設定に関わらずユーザーのフォローを誰でも見れる', async () => { { - await api('/i/update', { + await api('i/update', { followingVisibility: 'public', followersVisibility: 'public', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, bob); assert.strictEqual(followingRes.status, 200); assert.strictEqual(Array.isArray(followingRes.body), true); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'public', followersVisibility: 'followers', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, bob); assert.strictEqual(followingRes.status, 200); assert.strictEqual(Array.isArray(followingRes.body), true); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'public', followersVisibility: 'private', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, bob); assert.strictEqual(followingRes.status, 200); @@ -78,36 +78,36 @@ describe('FF visibility', () => { test('followersVisibility が public であれば followingVisibility の設定に関わらずユーザーのフォロワーを誰でも見れる', async () => { { - await api('/i/update', { + await api('i/update', { followingVisibility: 'public', followersVisibility: 'public', }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, bob); assert.strictEqual(followersRes.status, 200); assert.strictEqual(Array.isArray(followersRes.body), true); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'public', }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, bob); assert.strictEqual(followersRes.status, 200); assert.strictEqual(Array.isArray(followersRes.body), true); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'private', followersVisibility: 'public', }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, bob); assert.strictEqual(followersRes.status, 200); @@ -116,15 +116,15 @@ describe('FF visibility', () => { }); test('followingVisibility, followersVisibility がともに followers なユーザーのフォロー/フォロワーを自分で見れる', async () => { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'followers', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, alice); @@ -136,36 +136,36 @@ describe('FF visibility', () => { test('followingVisibility が followers なユーザーのフォローを followersVisibility の設定に関わらず自分で見れる', async () => { { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'public', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, alice); assert.strictEqual(followingRes.status, 200); assert.strictEqual(Array.isArray(followingRes.body), true); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'followers', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, alice); assert.strictEqual(followingRes.status, 200); assert.strictEqual(Array.isArray(followingRes.body), true); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'private', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, alice); assert.strictEqual(followingRes.status, 200); @@ -175,36 +175,36 @@ describe('FF visibility', () => { test('followersVisibility が followers なユーザーのフォロワーを followingVisibility の設定に関わらず自分で見れる', async () => { { - await api('/i/update', { + await api('i/update', { followingVisibility: 'public', followersVisibility: 'followers', }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, alice); assert.strictEqual(followersRes.status, 200); assert.strictEqual(Array.isArray(followersRes.body), true); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'followers', }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, alice); assert.strictEqual(followersRes.status, 200); assert.strictEqual(Array.isArray(followersRes.body), true); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'private', followersVisibility: 'followers', }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, alice); assert.strictEqual(followersRes.status, 200); @@ -213,15 +213,15 @@ describe('FF visibility', () => { }); test('followingVisibility, followersVisibility がともに followers なユーザーのフォロー/フォロワーを非フォロワーが見れない', async () => { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'followers', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, bob); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, bob); @@ -231,34 +231,34 @@ describe('FF visibility', () => { test('followingVisibility が followers なユーザーのフォローを followersVisibility の設定に関わらず非フォロワーが見れない', async () => { { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'public', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, bob); assert.strictEqual(followingRes.status, 400); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'followers', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, bob); assert.strictEqual(followingRes.status, 400); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'private', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, bob); assert.strictEqual(followingRes.status, 400); @@ -267,34 +267,34 @@ describe('FF visibility', () => { test('followersVisibility が followers なユーザーのフォロワーを followingVisibility の設定に関わらず非フォロワーが見れない', async () => { { - await api('/i/update', { + await api('i/update', { followingVisibility: 'public', followersVisibility: 'followers', }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, bob); assert.strictEqual(followersRes.status, 400); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'followers', }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, bob); assert.strictEqual(followersRes.status, 400); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'private', followersVisibility: 'followers', }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, bob); assert.strictEqual(followersRes.status, 400); @@ -302,19 +302,19 @@ describe('FF visibility', () => { }); test('followingVisibility, followersVisibility がともに followers なユーザーのフォロー/フォロワーをフォロワーが見れる', async () => { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'followers', }, alice); - await api('/following/create', { + await api('following/create', { userId: alice.id, }, bob); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, bob); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, bob); @@ -326,45 +326,45 @@ describe('FF visibility', () => { test('followingVisibility が followers なユーザーのフォローを followersVisibility の設定に関わらずフォロワーが見れる', async () => { { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'public', }, alice); - await api('/following/create', { + await api('following/create', { userId: alice.id, }, bob); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, bob); assert.strictEqual(followingRes.status, 200); assert.strictEqual(Array.isArray(followingRes.body), true); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'followers', }, alice); - await api('/following/create', { + await api('following/create', { userId: alice.id, }, bob); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, bob); assert.strictEqual(followingRes.status, 200); assert.strictEqual(Array.isArray(followingRes.body), true); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'private', }, alice); - await api('/following/create', { + await api('following/create', { userId: alice.id, }, bob); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, bob); assert.strictEqual(followingRes.status, 200); @@ -374,45 +374,45 @@ describe('FF visibility', () => { test('followersVisibility が followers なユーザーのフォロワーを followingVisibility の設定に関わらずフォロワーが見れる', async () => { { - await api('/i/update', { + await api('i/update', { followingVisibility: 'public', followersVisibility: 'followers', }, alice); - await api('/following/create', { + await api('following/create', { userId: alice.id, }, bob); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, bob); assert.strictEqual(followersRes.status, 200); assert.strictEqual(Array.isArray(followersRes.body), true); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'followers', }, alice); - await api('/following/create', { + await api('following/create', { userId: alice.id, }, bob); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, bob); assert.strictEqual(followersRes.status, 200); assert.strictEqual(Array.isArray(followersRes.body), true); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'private', followersVisibility: 'followers', }, alice); - await api('/following/create', { + await api('following/create', { userId: alice.id, }, bob); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, bob); assert.strictEqual(followersRes.status, 200); @@ -421,15 +421,15 @@ describe('FF visibility', () => { }); test('followingVisibility, followersVisibility がともに private なユーザーのフォロー/フォロワーを自分で見れる', async () => { - await api('/i/update', { + await api('i/update', { followingVisibility: 'private', followersVisibility: 'private', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, alice); @@ -441,36 +441,36 @@ describe('FF visibility', () => { test('followingVisibility が private なユーザーのフォローを followersVisibility の設定に関わらず自分で見れる', async () => { { - await api('/i/update', { + await api('i/update', { followingVisibility: 'private', followersVisibility: 'public', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, alice); assert.strictEqual(followingRes.status, 200); assert.strictEqual(Array.isArray(followingRes.body), true); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'private', followersVisibility: 'followers', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, alice); assert.strictEqual(followingRes.status, 200); assert.strictEqual(Array.isArray(followingRes.body), true); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'private', followersVisibility: 'private', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, alice); assert.strictEqual(followingRes.status, 200); @@ -480,36 +480,36 @@ describe('FF visibility', () => { test('followersVisibility が private なユーザーのフォロワーを followingVisibility の設定に関わらず自分で見れる', async () => { { - await api('/i/update', { + await api('i/update', { followingVisibility: 'public', followersVisibility: 'private', }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, alice); assert.strictEqual(followersRes.status, 200); assert.strictEqual(Array.isArray(followersRes.body), true); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'private', }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, alice); assert.strictEqual(followersRes.status, 200); assert.strictEqual(Array.isArray(followersRes.body), true); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'private', followersVisibility: 'private', }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, alice); assert.strictEqual(followersRes.status, 200); @@ -518,15 +518,15 @@ describe('FF visibility', () => { }); test('followingVisibility, followersVisibility がともに private なユーザーのフォロー/フォロワーを他人が見れない', async () => { - await api('/i/update', { + await api('i/update', { followingVisibility: 'private', followersVisibility: 'private', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, bob); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, bob); @@ -536,34 +536,34 @@ describe('FF visibility', () => { test('followingVisibility が private なユーザーのフォローを followersVisibility の設定に関わらず他人が見れない', async () => { { - await api('/i/update', { + await api('i/update', { followingVisibility: 'private', followersVisibility: 'public', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, bob); assert.strictEqual(followingRes.status, 400); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'private', followersVisibility: 'followers', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, bob); assert.strictEqual(followingRes.status, 400); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'private', followersVisibility: 'private', }, alice); - const followingRes = await api('/users/following', { + const followingRes = await api('users/following', { userId: alice.id, }, bob); assert.strictEqual(followingRes.status, 400); @@ -572,34 +572,34 @@ describe('FF visibility', () => { test('followersVisibility が private なユーザーのフォロワーを followingVisibility の設定に関わらず他人が見れない', async () => { { - await api('/i/update', { + await api('i/update', { followingVisibility: 'public', followersVisibility: 'private', }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, bob); assert.strictEqual(followersRes.status, 400); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', followersVisibility: 'private', }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, bob); assert.strictEqual(followersRes.status, 400); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'private', followersVisibility: 'private', }, alice); - const followersRes = await api('/users/followers', { + const followersRes = await api('users/followers', { userId: alice.id, }, bob); assert.strictEqual(followersRes.status, 400); @@ -609,7 +609,7 @@ describe('FF visibility', () => { describe('AP', () => { test('followingVisibility が public 以外ならばAPからはフォローを取得できない', async () => { { - await api('/i/update', { + await api('i/update', { followingVisibility: 'public', }, alice); @@ -617,7 +617,7 @@ describe('FF visibility', () => { assert.strictEqual(followingRes.status, 200); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'followers', }, alice); @@ -625,7 +625,7 @@ describe('FF visibility', () => { assert.strictEqual(followingRes.status, 403); } { - await api('/i/update', { + await api('i/update', { followingVisibility: 'private', }, alice); @@ -636,7 +636,7 @@ describe('FF visibility', () => { test('followersVisibility が public 以外ならばAPからはフォロワーを取得できない', async () => { { - await api('/i/update', { + await api('i/update', { followersVisibility: 'public', }, alice); @@ -644,7 +644,7 @@ describe('FF visibility', () => { assert.strictEqual(followersRes.status, 200); } { - await api('/i/update', { + await api('i/update', { followersVisibility: 'followers', }, alice); @@ -652,7 +652,7 @@ describe('FF visibility', () => { assert.strictEqual(followersRes.status, 403); } { - await api('/i/update', { + await api('i/update', { followersVisibility: 'private', }, alice); diff --git a/packages/backend/test/e2e/move.ts b/packages/backend/test/e2e/move.ts index f6417e39b5ec..4e5306da97da 100644 --- a/packages/backend/test/e2e/move.ts +++ b/packages/backend/test/e2e/move.ts @@ -55,7 +55,7 @@ describe('Account Move', () => { }, 1000 * 10); test('Able to create an alias', async () => { - const res = await api('/i/update', { + const res = await api('i/update', { alsoKnownAs: [`@alice@${url.hostname}`], }, bob); @@ -67,7 +67,7 @@ describe('Account Move', () => { }); test('Able to create a local alias without hostname', async () => { - await api('/i/update', { + await api('i/update', { alsoKnownAs: ['@alice'], }, bob); @@ -77,7 +77,7 @@ describe('Account Move', () => { }); test('Able to create a local alias without @', async () => { - await api('/i/update', { + await api('i/update', { alsoKnownAs: ['alice'], }, bob); @@ -87,7 +87,7 @@ describe('Account Move', () => { }); test('Able to set remote user (but may fail)', async () => { - const res = await api('/i/update', { + const res = await api('i/update', { alsoKnownAs: ['@syuilo@example.com'], }, bob); @@ -97,7 +97,7 @@ describe('Account Move', () => { }); test('Unable to add duplicated aliases to alsoKnownAs', async () => { - const res = await api('/i/update', { + const res = await api('i/update', { alsoKnownAs: [`@alice@${url.hostname}`, `@alice@${url.hostname}`], }, bob); @@ -107,7 +107,7 @@ describe('Account Move', () => { }); test('Unable to add itself', async () => { - const res = await api('/i/update', { + const res = await api('i/update', { alsoKnownAs: [`@bob@${url.hostname}`], }, bob); @@ -117,7 +117,7 @@ describe('Account Move', () => { }); test('Unable to add a nonexisting local account to alsoKnownAs', async () => { - const res1 = await api('/i/update', { + const res1 = await api('i/update', { alsoKnownAs: [`@nonexist@${url.hostname}`], }, bob); @@ -125,7 +125,7 @@ describe('Account Move', () => { assert.strictEqual(res1.body.error.code, 'NO_SUCH_USER'); assert.strictEqual(res1.body.error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5'); - const res2 = await api('/i/update', { + const res2 = await api('i/update', { alsoKnownAs: ['@alice', 'nonexist'], }, bob); @@ -135,7 +135,7 @@ describe('Account Move', () => { }); test('Able to add two existing local account to alsoKnownAs', async () => { - await api('/i/update', { + await api('i/update', { alsoKnownAs: [`@alice@${url.hostname}`, `@carol@${url.hostname}`], }, bob); @@ -146,10 +146,10 @@ describe('Account Move', () => { }); test('Able to properly overwrite alsoKnownAs', async () => { - await api('/i/update', { + await api('i/update', { alsoKnownAs: [`@alice@${url.hostname}`], }, bob); - await api('/i/update', { + await api('i/update', { alsoKnownAs: [`@carol@${url.hostname}`, `@dave@${url.hostname}`], }, bob); @@ -164,27 +164,27 @@ describe('Account Move', () => { let antennaId = ''; beforeAll(async () => { - await api('/i/update', { + await api('i/update', { alsoKnownAs: [`@alice@${url.hostname}`], }, root); - const listRoot = await api('/users/lists/create', { + const listRoot = await api('users/lists/create', { name: secureRndstr(8), }, root); - await api('/users/lists/push', { + await api('users/lists/push', { listId: listRoot.body.id, userId: alice.id, }, root); - await api('/following/create', { + await api('following/create', { userId: root.id, }, alice); - await api('/following/create', { + await api('following/create', { userId: eve.id, }, alice); - const antenna = await api('/antennas/create', { + const antenna = await api('antennas/create', { name: secureRndstr(8), src: 'home', - keywords: [secureRndstr(8)], + keywords: [[secureRndstr(8)]], excludeKeywords: [], users: [], caseSensitive: false, @@ -195,48 +195,48 @@ describe('Account Move', () => { }, alice); antennaId = antenna.body.id; - await api('/i/update', { + await api('i/update', { alsoKnownAs: [`@alice@${url.hostname}`], }, bob); - await api('/following/create', { + await api('following/create', { userId: alice.id, }, carol); - await api('/mute/create', { + await api('mute/create', { userId: alice.id, }, dave); - await api('/blocking/create', { + await api('blocking/create', { userId: alice.id, }, dave); - await api('/following/create', { + await api('following/create', { userId: eve.id, }, dave); - await api('/following/create', { + await api('following/create', { userId: dave.id, }, eve); - const listEve = await api('/users/lists/create', { + const listEve = await api('users/lists/create', { name: secureRndstr(8), }, eve); - await api('/users/lists/push', { + await api('users/lists/push', { listId: listEve.body.id, userId: bob.id, }, eve); - await api('/i/update', { + await api('i/update', { isLocked: true, }, frank); - await api('/following/create', { + await api('following/create', { userId: frank.id, }, alice); - await api('/following/requests/accept', { + await api('following/requests/accept', { userId: alice.id, }, frank); }, 1000 * 10); test('Prohibit the root account from moving', async () => { - const res = await api('/i/move', { + const res = await api('i/move', { moveToAccount: `@bob@${url.hostname}`, }, root); @@ -246,7 +246,7 @@ describe('Account Move', () => { }); test('Unable to move to a nonexisting local account', async () => { - const res = await api('/i/move', { + const res = await api('i/move', { moveToAccount: `@nonexist@${url.hostname}`, }, alice); @@ -256,7 +256,7 @@ describe('Account Move', () => { }); test('Unable to move if alsoKnownAs is invalid', async () => { - const res = await api('/i/move', { + const res = await api('i/move', { moveToAccount: `@carol@${url.hostname}`, }, alice); @@ -266,7 +266,7 @@ describe('Account Move', () => { }); test('Relationships have been properly migrated', async () => { - const move = await api('/i/move', { + const move = await api('i/move', { moveToAccount: `@bob@${url.hostname}`, }, alice); @@ -275,13 +275,13 @@ describe('Account Move', () => { await sleep(1000 * 3); // wait for jobs to finish // Unfollow delayed? - const aliceFollowings = await api('/users/following', { + const aliceFollowings = await api('users/following', { userId: alice.id, }, alice); assert.strictEqual(aliceFollowings.status, 200); assert.strictEqual(aliceFollowings.body.length, 3); - const carolFollowings = await api('/users/following', { + const carolFollowings = await api('users/following', { userId: carol.id, }, carol); assert.strictEqual(carolFollowings.status, 200); @@ -289,25 +289,25 @@ describe('Account Move', () => { assert.strictEqual(carolFollowings.body[0].followeeId, bob.id); assert.strictEqual(carolFollowings.body[1].followeeId, alice.id); - const blockings = await api('/blocking/list', {}, dave); + const blockings = await api('blocking/list', {}, dave); assert.strictEqual(blockings.status, 200); assert.strictEqual(blockings.body.length, 2); assert.strictEqual(blockings.body[0].blockeeId, bob.id); assert.strictEqual(blockings.body[1].blockeeId, alice.id); - const mutings = await api('/mute/list', {}, dave); + const mutings = await api('mute/list', {}, dave); assert.strictEqual(mutings.status, 200); assert.strictEqual(mutings.body.length, 2); assert.strictEqual(mutings.body[0].muteeId, bob.id); assert.strictEqual(mutings.body[1].muteeId, alice.id); - const rootLists = await api('/users/lists/list', {}, root); + const rootLists = await api('users/lists/list', {}, root); assert.strictEqual(rootLists.status, 200); assert.strictEqual(rootLists.body[0].userIds.length, 2); assert.ok(rootLists.body[0].userIds.find((id: string) => id === bob.id)); assert.ok(rootLists.body[0].userIds.find((id: string) => id === alice.id)); - const eveLists = await api('/users/lists/list', {}, eve); + const eveLists = await api('users/lists/list', {}, eve); assert.strictEqual(eveLists.status, 200); assert.strictEqual(eveLists.body[0].userIds.length, 1); assert.ok(eveLists.body[0].userIds.find((id: string) => id === bob.id)); @@ -315,13 +315,13 @@ describe('Account Move', () => { test('A locked account automatically accept the follow request if it had already accepted the old account.', async () => { await successfulApiCall({ - endpoint: '/following/create', + endpoint: 'following/create', parameters: { userId: frank.id, }, user: bob, }); - const followers = await api('/users/followers', { + const followers = await api('users/followers', { userId: frank.id, }, frank); @@ -333,7 +333,7 @@ describe('Account Move', () => { test('Unfollowed after 10 sec (24 hours in production).', async () => { await sleep(1000 * 8); - const following = await api('/users/following', { + const following = await api('users/following', { userId: alice.id, }, alice); @@ -342,7 +342,7 @@ describe('Account Move', () => { }); test('Unable to move if the destination account has already moved.', async () => { - const res = await api('/i/move', { + const res = await api('i/move', { moveToAccount: `@alice@${url.hostname}`, }, bob); @@ -352,7 +352,7 @@ describe('Account Move', () => { }); test('Follow and follower counts are properly adjusted', async () => { - await api('/following/create', { + await api('following/create', { userId: alice.id, }, eve); const newAlice = await Users.findOneByOrFail({ id: alice.id }); @@ -365,7 +365,7 @@ describe('Account Move', () => { assert.strictEqual(newEve.followingCount, 1); assert.strictEqual(newEve.followersCount, 1); - await api('/following/delete', { + await api('following/delete', { userId: alice.id, }, eve); newEve = await Users.findOneByOrFail({ id: eve.id }); @@ -374,49 +374,49 @@ describe('Account Move', () => { }); test.each([ - '/antennas/create', - '/channels/create', - '/channels/favorite', - '/channels/follow', - '/channels/unfavorite', - '/channels/unfollow', - '/clips/add-note', - '/clips/create', - '/clips/favorite', - '/clips/remove-note', - '/clips/unfavorite', - '/clips/update', - '/drive/files/upload-from-url', - '/flash/create', - '/flash/like', - '/flash/unlike', - '/flash/update', - '/following/create', - '/gallery/posts/create', - '/gallery/posts/like', - '/gallery/posts/unlike', - '/gallery/posts/update', - '/i/claim-achievement', - '/i/move', - '/i/import-blocking', - '/i/import-following', - '/i/import-muting', - '/i/import-user-lists', - '/i/pin', - '/mute/create', - '/notes/create', - '/notes/favorites/create', - '/notes/polls/vote', - '/notes/reactions/create', - '/pages/create', - '/pages/like', - '/pages/unlike', - '/pages/update', - '/renote-mute/create', - '/users/lists/create', - '/users/lists/pull', - '/users/lists/push', - ])('Prohibit access after moving: %s', async (endpoint) => { + 'antennas/create', + 'channels/create', + 'channels/favorite', + 'channels/follow', + 'channels/unfavorite', + 'channels/unfollow', + 'clips/add-note', + 'clips/create', + 'clips/favorite', + 'clips/remove-note', + 'clips/unfavorite', + 'clips/update', + 'drive/files/upload-from-url', + 'flash/create', + 'flash/like', + 'flash/unlike', + 'flash/update', + 'following/create', + 'gallery/posts/create', + 'gallery/posts/like', + 'gallery/posts/unlike', + 'gallery/posts/update', + 'i/claim-achievement', + 'i/move', + 'i/import-blocking', + 'i/import-following', + 'i/import-muting', + 'i/import-user-lists', + 'i/pin', + 'mute/create', + 'notes/create', + 'notes/favorites/create', + 'notes/polls/vote', + 'notes/reactions/create', + 'pages/create', + 'pages/like', + 'pages/unlike', + 'pages/update', + 'renote-mute/create', + 'users/lists/create', + 'users/lists/pull', + 'users/lists/push', + ] as const)('Prohibit access after moving: %s', async (endpoint) => { const res = await api(endpoint, {}, alice); assert.strictEqual(res.status, 403); assert.strictEqual(res.body.error.code, 'YOUR_ACCOUNT_MOVED'); @@ -424,11 +424,11 @@ describe('Account Move', () => { }); test('Prohibit access after moving: /antennas/update', async () => { - const res = await api('/antennas/update', { + const res = await api('antennas/update', { antennaId, name: secureRndstr(8), src: 'users', - keywords: [secureRndstr(8)], + keywords: [[secureRndstr(8)]], excludeKeywords: [], users: [eve.id], caseSensitive: false, @@ -447,12 +447,12 @@ describe('Account Move', () => { const res = await uploadFile(alice); assert.strictEqual(res.status, 403); - assert.strictEqual(res.body.error.code, 'YOUR_ACCOUNT_MOVED'); - assert.strictEqual(res.body.error.id, '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31'); + assert.strictEqual((res.body! as any as { error: misskey.api.APIError }).error.code, 'YOUR_ACCOUNT_MOVED'); + assert.strictEqual((res.body! as any as { error: misskey.api.APIError }).error.id, '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31'); }); test('Prohibit updating alsoKnownAs after moving', async () => { - const res = await api('/i/update', { + const res = await api('i/update', { alsoKnownAs: [`@eve@${url.hostname}`], }, alice); diff --git a/packages/backend/test/e2e/mute.ts b/packages/backend/test/e2e/mute.ts index 1d28e07b7d72..0e52c5decc37 100644 --- a/packages/backend/test/e2e/mute.ts +++ b/packages/backend/test/e2e/mute.ts @@ -19,21 +19,31 @@ describe('Mute', () => { alice = await signup({ username: 'alice' }); bob = await signup({ username: 'bob' }); carol = await signup({ username: 'carol' }); + + // Mute: alice ==> carol + await api('mute/create', { + userId: carol.id, + }, alice); }, 1000 * 60 * 2); test('ミュート作成', async () => { - const res = await api('/mute/create', { - userId: carol.id, + const res = await api('mute/create', { + userId: bob.id, }, alice); assert.strictEqual(res.status, 204); + + // 単体でも走らせられるように副作用消す + await api('mute/delete', { + userId: bob.id, + }, alice); }); test('「自分宛ての投稿」にミュートしているユーザーの投稿が含まれない', async () => { const bobNote = await post(bob, { text: '@alice hi' }); const carolNote = await post(carol, { text: '@alice hi' }); - const res = await api('/notes/mentions', {}, alice); + const res = await api('notes/mentions', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); @@ -43,11 +53,11 @@ describe('Mute', () => { test('ミュートしているユーザーからメンションされても、hasUnreadMentions が true にならない', async () => { // 状態リセット - await api('/i/read-all-unread-notes', {}, alice); + await api('i/read-all-unread-notes', {}, alice); await post(carol, { text: '@alice hi' }); - const res = await api('/i', {}, alice); + const res = await api('i', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(res.body.hasUnreadMentions, false); @@ -55,7 +65,7 @@ describe('Mute', () => { test('ミュートしているユーザーからメンションされても、ストリームに unreadMention イベントが流れてこない', async () => { // 状態リセット - await api('/i/read-all-unread-notes', {}, alice); + await api('i/read-all-unread-notes', {}, alice); const fired = await waitFire(alice, 'main', () => post(carol, { text: '@alice hi' }), msg => msg.type === 'unreadMention'); @@ -64,8 +74,8 @@ describe('Mute', () => { test('ミュートしているユーザーからメンションされても、ストリームに unreadNotification イベントが流れてこない', async () => { // 状態リセット - await api('/i/read-all-unread-notes', {}, alice); - await api('/notifications/mark-all-as-read', {}, alice); + await api('i/read-all-unread-notes', {}, alice); + await api('notifications/mark-all-as-read', {}, alice); const fired = await waitFire(alice, 'main', () => post(carol, { text: '@alice hi' }), msg => msg.type === 'unreadNotification'); @@ -78,7 +88,7 @@ describe('Mute', () => { const bobNote = await post(bob, { text: 'hi' }); const carolNote = await post(carol, { text: 'hi' }); - const res = await api('/notes/local-timeline', {}, alice); + const res = await api('notes/local-timeline', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); @@ -94,7 +104,7 @@ describe('Mute', () => { renoteId: carolNote.id, }); - const res = await api('/notes/local-timeline', {}, alice); + const res = await api('notes/local-timeline', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); @@ -110,7 +120,7 @@ describe('Mute', () => { await react(bob, aliceNote, 'like'); await react(carol, aliceNote, 'like'); - const res = await api('/i/notifications', {}, alice); + const res = await api('i/notifications', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); @@ -123,7 +133,7 @@ describe('Mute', () => { await post(bob, { text: '@alice hi', replyId: aliceNote.id }); await post(carol, { text: '@alice hi', replyId: aliceNote.id }); - const res = await api('/i/notifications', {}, alice); + const res = await api('i/notifications', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); @@ -137,7 +147,7 @@ describe('Mute', () => { await post(bob, { text: '@alice hi' }); await post(carol, { text: '@alice hi' }); - const res = await api('/i/notifications', {}, alice); + const res = await api('i/notifications', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); @@ -151,7 +161,7 @@ describe('Mute', () => { await post(bob, { text: 'hi', renoteId: aliceNote.id }); await post(carol, { text: 'hi', renoteId: aliceNote.id }); - const res = await api('/i/notifications', {}, alice); + const res = await api('i/notifications', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); @@ -165,7 +175,7 @@ describe('Mute', () => { await post(bob, { renoteId: aliceNote.id }); await post(carol, { renoteId: aliceNote.id }); - const res = await api('/i/notifications', {}, alice); + const res = await api('i/notifications', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); @@ -175,30 +185,36 @@ describe('Mute', () => { }); test('通知にミュートしているユーザーからのフォロー通知が含まれない', async () => { - await api('/i/follow', { userId: alice.id }, bob); - await api('/i/follow', { userId: alice.id }, carol); + await api('following/create', { userId: alice.id }, bob); + await api('following/create', { userId: alice.id }, carol); - const res = await api('/i/notifications', {}, alice); + const res = await api('i/notifications', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true); assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false); + + await api('following/delete', { userId: alice.id }, bob); + await api('following/delete', { userId: alice.id }, carol); }); test('通知にミュートしているユーザーからのフォローリクエストが含まれない', async () => { - await api('/i/update/', { isLocked: true }, alice); - await api('/following/create', { userId: alice.id }, bob); - await api('/following/create', { userId: alice.id }, carol); + await api('i/update', { isLocked: true }, alice); + await api('following/create', { userId: alice.id }, bob); + await api('following/create', { userId: alice.id }, carol); - const res = await api('/i/notifications', {}, alice); + const res = await api('i/notifications', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true); assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false); + + await api('following/delete', { userId: alice.id }, bob); + await api('following/delete', { userId: alice.id }, carol); }); }); @@ -208,7 +224,7 @@ describe('Mute', () => { await react(bob, aliceNote, 'like'); await react(carol, aliceNote, 'like'); - const res = await api('/i/notifications-grouped', {}, alice); + const res = await api('i/notifications-grouped', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); @@ -220,7 +236,7 @@ describe('Mute', () => { await post(bob, { text: '@alice hi', replyId: aliceNote.id }); await post(carol, { text: '@alice hi', replyId: aliceNote.id }); - const res = await api('/i/notifications-grouped', {}, alice); + const res = await api('i/notifications-grouped', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); @@ -234,7 +250,7 @@ describe('Mute', () => { await post(bob, { text: '@alice hi' }); await post(carol, { text: '@alice hi' }); - const res = await api('/i/notifications-grouped', {}, alice); + const res = await api('i/notifications-grouped', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); @@ -248,7 +264,7 @@ describe('Mute', () => { await post(bob, { text: 'hi', renoteId: aliceNote.id }); await post(carol, { text: 'hi', renoteId: aliceNote.id }); - const res = await api('/i/notifications-grouped', {}, alice); + const res = await api('i/notifications-grouped', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); @@ -262,7 +278,7 @@ describe('Mute', () => { await post(bob, { renoteId: aliceNote.id }); await post(carol, { renoteId: aliceNote.id }); - const res = await api('/i/notifications-grouped', {}, alice); + const res = await api('i/notifications-grouped', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); @@ -272,24 +288,27 @@ describe('Mute', () => { }); test('通知にミュートしているユーザーからのフォロー通知が含まれない', async () => { - await api('/i/follow', { userId: alice.id }, bob); - await api('/i/follow', { userId: alice.id }, carol); + await api('following/create', { userId: alice.id }, bob); + await api('following/create', { userId: alice.id }, carol); - const res = await api('/i/notifications-grouped', {}, alice); + const res = await api('i/notifications-grouped', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true); assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false); + + await api('following/delete', { userId: alice.id }, bob); + await api('following/delete', { userId: alice.id }, carol); }); test('通知にミュートしているユーザーからのフォローリクエストが含まれない', async () => { - await api('/i/update/', { isLocked: true }, alice); - await api('/following/create', { userId: alice.id }, bob); - await api('/following/create', { userId: alice.id }, carol); + await api('i/update', { isLocked: true }, alice); + await api('following/create', { userId: alice.id }, bob); + await api('following/create', { userId: alice.id }, carol); - const res = await api('/i/notifications-grouped', {}, alice); + const res = await api('i/notifications-grouped', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); diff --git a/packages/backend/test/e2e/note.ts b/packages/backend/test/e2e/note.ts index 2406204f4101..973bcbd750cf 100644 --- a/packages/backend/test/e2e/note.ts +++ b/packages/backend/test/e2e/note.ts @@ -31,7 +31,7 @@ describe('Note', () => { text: 'test', }; - const res = await api('/notes/create', post, alice); + const res = await api('notes/create', post, alice); assert.strictEqual(res.status, 200); assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true); @@ -41,7 +41,7 @@ describe('Note', () => { test('ファイルを添付できる', async () => { const file = await uploadUrl(alice, 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg'); - const res = await api('/notes/create', { + const res = await api('notes/create', { fileIds: [file.id], }, alice); @@ -53,7 +53,7 @@ describe('Note', () => { test('他人のファイルで怒られる', async () => { const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg'); - const res = await api('/notes/create', { + const res = await api('notes/create', { text: 'test', fileIds: [file.id], }, alice); @@ -64,7 +64,7 @@ describe('Note', () => { }, 1000 * 10); test('存在しないファイルで怒られる', async () => { - const res = await api('/notes/create', { + const res = await api('notes/create', { text: 'test', fileIds: ['000000000000000000000000'], }, alice); @@ -75,7 +75,7 @@ describe('Note', () => { }); test('不正なファイルIDで怒られる', async () => { - const res = await api('/notes/create', { + const res = await api('notes/create', { fileIds: ['kyoppie'], }, alice); assert.strictEqual(res.status, 400); @@ -93,7 +93,7 @@ describe('Note', () => { replyId: bobPost.id, }; - const res = await api('/notes/create', alicePost, alice); + const res = await api('notes/create', alicePost, alice); assert.strictEqual(res.status, 200); assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true); @@ -111,7 +111,7 @@ describe('Note', () => { renoteId: bobPost.id, }; - const res = await api('/notes/create', alicePost, alice); + const res = await api('notes/create', alicePost, alice); assert.strictEqual(res.status, 200); assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true); @@ -129,7 +129,7 @@ describe('Note', () => { renoteId: bobPost.id, }; - const res = await api('/notes/create', alicePost, alice); + const res = await api('notes/create', alicePost, alice); assert.strictEqual(res.status, 200); assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true); @@ -142,7 +142,7 @@ describe('Note', () => { const bobPost = await post(bob, { text: 'test', }); - const res = await api('/notes/create', { + const res = await api('notes/create', { text: ' ', renoteId: bobPost.id, }, alice); @@ -152,7 +152,7 @@ describe('Note', () => { }); test('visibility: followersでrenoteできる', async () => { - const createRes = await api('/notes/create', { + const createRes = await api('notes/create', { text: 'test', visibility: 'followers', }, alice); @@ -160,7 +160,7 @@ describe('Note', () => { assert.strictEqual(createRes.status, 200); const renoteId = createRes.body.createdNote.id; - const renoteRes = await api('/notes/create', { + const renoteRes = await api('notes/create', { visibility: 'followers', renoteId, }, alice); @@ -169,7 +169,7 @@ describe('Note', () => { assert.strictEqual(renoteRes.body.createdNote.renoteId, renoteId); assert.strictEqual(renoteRes.body.createdNote.visibility, 'followers'); - const deleteRes = await api('/notes/delete', { + const deleteRes = await api('notes/delete', { noteId: renoteRes.body.createdNote.id, }, alice); @@ -177,11 +177,11 @@ describe('Note', () => { }); test('visibility: followersなノートに対してフォロワーはリプライできる', async () => { - await api('/following/create', { + await api('following/create', { userId: alice.id, }, bob); - const aliceNote = await api('/notes/create', { + const aliceNote = await api('notes/create', { text: 'direct note to bob', visibility: 'followers', }, alice); @@ -189,7 +189,7 @@ describe('Note', () => { assert.strictEqual(aliceNote.status, 200); const replyId = aliceNote.body.createdNote.id; - const bobReply = await api('/notes/create', { + const bobReply = await api('notes/create', { text: 'reply to alice note', replyId, }, bob); @@ -197,20 +197,20 @@ describe('Note', () => { assert.strictEqual(bobReply.status, 200); assert.strictEqual(bobReply.body.createdNote.replyId, replyId); - await api('/following/delete', { + await api('following/delete', { userId: alice.id, }, bob); }); test('visibility: followersなノートに対してフォロワーでないユーザーがリプライしようとすると怒られる', async () => { - const aliceNote = await api('/notes/create', { + const aliceNote = await api('notes/create', { text: 'direct note to bob', visibility: 'followers', }, alice); assert.strictEqual(aliceNote.status, 200); - const bobReply = await api('/notes/create', { + const bobReply = await api('notes/create', { text: 'reply to alice note', replyId: aliceNote.body.createdNote.id, }, bob); @@ -220,7 +220,7 @@ describe('Note', () => { }); test('visibility: specifiedなノートに対してvisibility: specifiedで返信できる', async () => { - const aliceNote = await api('/notes/create', { + const aliceNote = await api('notes/create', { text: 'direct note to bob', visibility: 'specified', visibleUserIds: [bob.id], @@ -228,7 +228,7 @@ describe('Note', () => { assert.strictEqual(aliceNote.status, 200); - const bobReply = await api('/notes/create', { + const bobReply = await api('notes/create', { text: 'reply to alice note', replyId: aliceNote.body.createdNote.id, visibility: 'specified', @@ -239,7 +239,7 @@ describe('Note', () => { }); test('visibility: specifiedなノートに対してvisibility: follwersで返信しようとすると怒られる', async () => { - const aliceNote = await api('/notes/create', { + const aliceNote = await api('notes/create', { text: 'direct note to bob', visibility: 'specified', visibleUserIds: [bob.id], @@ -247,7 +247,7 @@ describe('Note', () => { assert.strictEqual(aliceNote.status, 200); - const bobReply = await api('/notes/create', { + const bobReply = await api('notes/create', { text: 'reply to alice note with visibility: followers', replyId: aliceNote.body.createdNote.id, visibility: 'followers', @@ -261,7 +261,7 @@ describe('Note', () => { const post = { text: '!'.repeat(MAX_NOTE_TEXT_LENGTH), // 3000文字 }; - const res = await api('/notes/create', post, alice); + const res = await api('notes/create', post, alice); assert.strictEqual(res.status, 200); }); @@ -269,7 +269,7 @@ describe('Note', () => { const post = { text: '!'.repeat(MAX_NOTE_TEXT_LENGTH + 1), // 3001文字 }; - const res = await api('/notes/create', post, alice); + const res = await api('notes/create', post, alice); assert.strictEqual(res.status, 400); }); @@ -278,7 +278,7 @@ describe('Note', () => { text: 'test', replyId: '000000000000000000000000', }; - const res = await api('/notes/create', post, alice); + const res = await api('notes/create', post, alice); assert.strictEqual(res.status, 400); }); @@ -286,7 +286,7 @@ describe('Note', () => { const post = { renoteId: '000000000000000000000000', }; - const res = await api('/notes/create', post, alice); + const res = await api('notes/create', post, alice); assert.strictEqual(res.status, 400); }); @@ -295,7 +295,7 @@ describe('Note', () => { text: 'test', replyId: 'foo', }; - const res = await api('/notes/create', post, alice); + const res = await api('notes/create', post, alice); assert.strictEqual(res.status, 400); }); @@ -303,7 +303,7 @@ describe('Note', () => { const post = { renoteId: 'foo', }; - const res = await api('/notes/create', post, alice); + const res = await api('notes/create', post, alice); assert.strictEqual(res.status, 400); }); @@ -312,7 +312,7 @@ describe('Note', () => { text: '@ghost yo', }; - const res = await api('/notes/create', post, alice); + const res = await api('notes/create', post, alice); assert.strictEqual(res.status, 200); assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true); @@ -324,7 +324,7 @@ describe('Note', () => { text: '@bob @bob @bob yo', }; - const res = await api('/notes/create', post, alice); + const res = await api('notes/create', post, alice); assert.strictEqual(res.status, 200); assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true); @@ -337,25 +337,25 @@ describe('Note', () => { describe('添付ファイル情報', () => { test('ファイルを添付した場合、投稿成功時にファイル情報入りのレスポンスが帰ってくる', async () => { const file = await uploadFile(alice); - const res = await api('/notes/create', { - fileIds: [file.body.id], + const res = await api('notes/create', { + fileIds: [file.body!.id], }, alice); assert.strictEqual(res.status, 200); assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true); assert.strictEqual(res.body.createdNote.files.length, 1); - assert.strictEqual(res.body.createdNote.files[0].id, file.body.id); + assert.strictEqual(res.body.createdNote.files[0].id, file.body!.id); }); test('ファイルを添付した場合、タイムラインでファイル情報入りのレスポンスが帰ってくる', async () => { const file = await uploadFile(alice); - const createdNote = await api('/notes/create', { - fileIds: [file.body.id], + const createdNote = await api('notes/create', { + fileIds: [file.body!.id], }, alice); assert.strictEqual(createdNote.status, 200); - const res = await api('/notes', { + const res = await api('notes', { withFiles: true, }, alice); @@ -364,23 +364,23 @@ describe('Note', () => { const myNote = res.body.find((note: { id: string; files: { id: string }[] }) => note.id === createdNote.body.createdNote.id); assert.notEqual(myNote, null); assert.strictEqual(myNote.files.length, 1); - assert.strictEqual(myNote.files[0].id, file.body.id); + assert.strictEqual(myNote.files[0].id, file.body!.id); }); test('ファイルが添付されたノートをリノートした場合、タイムラインでファイル情報入りのレスポンスが帰ってくる', async () => { const file = await uploadFile(alice); - const createdNote = await api('/notes/create', { - fileIds: [file.body.id], + const createdNote = await api('notes/create', { + fileIds: [file.body!.id], }, alice); assert.strictEqual(createdNote.status, 200); - const renoted = await api('/notes/create', { + const renoted = await api('notes/create', { renoteId: createdNote.body.createdNote.id, }, alice); assert.strictEqual(renoted.status, 200); - const res = await api('/notes', { + const res = await api('notes', { renote: true, }, alice); @@ -389,24 +389,24 @@ describe('Note', () => { const myNote = res.body.find((note: { id: string }) => note.id === renoted.body.createdNote.id); assert.notEqual(myNote, null); assert.strictEqual(myNote.renote.files.length, 1); - assert.strictEqual(myNote.renote.files[0].id, file.body.id); + assert.strictEqual(myNote.renote.files[0].id, file.body!.id); }); test('ファイルが添付されたノートに返信した場合、タイムラインでファイル情報入りのレスポンスが帰ってくる', async () => { const file = await uploadFile(alice); - const createdNote = await api('/notes/create', { - fileIds: [file.body.id], + const createdNote = await api('notes/create', { + fileIds: [file.body!.id], }, alice); assert.strictEqual(createdNote.status, 200); - const reply = await api('/notes/create', { + const reply = await api('notes/create', { replyId: createdNote.body.createdNote.id, text: 'this is reply', }, alice); assert.strictEqual(reply.status, 200); - const res = await api('/notes', { + const res = await api('notes', { reply: true, }, alice); @@ -415,29 +415,29 @@ describe('Note', () => { const myNote = res.body.find((note: { id: string }) => note.id === reply.body.createdNote.id); assert.notEqual(myNote, null); assert.strictEqual(myNote.reply.files.length, 1); - assert.strictEqual(myNote.reply.files[0].id, file.body.id); + assert.strictEqual(myNote.reply.files[0].id, file.body!.id); }); test('ファイルが添付されたノートへの返信をリノートした場合、タイムラインでファイル情報入りのレスポンスが帰ってくる', async () => { const file = await uploadFile(alice); - const createdNote = await api('/notes/create', { - fileIds: [file.body.id], + const createdNote = await api('notes/create', { + fileIds: [file.body!.id], }, alice); assert.strictEqual(createdNote.status, 200); - const reply = await api('/notes/create', { + const reply = await api('notes/create', { replyId: createdNote.body.createdNote.id, text: 'this is reply', }, alice); assert.strictEqual(reply.status, 200); - const renoted = await api('/notes/create', { + const renoted = await api('notes/create', { renoteId: reply.body.createdNote.id, }, alice); assert.strictEqual(renoted.status, 200); - const res = await api('/notes', { + const res = await api('notes', { renote: true, }, alice); @@ -446,7 +446,7 @@ describe('Note', () => { const myNote = res.body.find((note: { id: string }) => note.id === renoted.body.createdNote.id); assert.notEqual(myNote, null); assert.strictEqual(myNote.renote.reply.files.length, 1); - assert.strictEqual(myNote.renote.reply.files[0].id, file.body.id); + assert.strictEqual(myNote.renote.reply.files[0].id, file.body!.id); }); test('NSFWが強制されている場合変更できない', async () => { @@ -483,15 +483,15 @@ describe('Note', () => { }, alice); assert.strictEqual(assign.status, 204); - assert.strictEqual(file.body.isSensitive, false); + assert.strictEqual(file.body!.isSensitive, false); const nsfwfile = await uploadFile(alice); assert.strictEqual(nsfwfile.status, 200); - assert.strictEqual(nsfwfile.body.isSensitive, true); + assert.strictEqual(nsfwfile.body!.isSensitive, true); const liftnsfw = await api('drive/files/update', { - fileId: nsfwfile.body.id, + fileId: nsfwfile.body!.id, isSensitive: false, }, alice); @@ -499,7 +499,7 @@ describe('Note', () => { assert.strictEqual(liftnsfw.body.error.code, 'RESTRICTED_BY_ROLE'); const oldaddnsfw = await api('drive/files/update', { - fileId: file.body.id, + fileId: file.body!.id, isSensitive: true, }, alice); @@ -518,7 +518,7 @@ describe('Note', () => { describe('notes/create', () => { test('投票を添付できる', async () => { - const res = await api('/notes/create', { + const res = await api('notes/create', { text: 'test', poll: { choices: ['foo', 'bar'], @@ -531,14 +531,15 @@ describe('Note', () => { }); test('投票の選択肢が無くて怒られる', async () => { - const res = await api('/notes/create', { + const res = await api('notes/create', { + // @ts-expect-error poll must not be empty poll: {}, }, alice); assert.strictEqual(res.status, 400); }); test('投票の選択肢が無くて怒られる (空の配列)', async () => { - const res = await api('/notes/create', { + const res = await api('notes/create', { poll: { choices: [], }, @@ -547,7 +548,7 @@ describe('Note', () => { }); test('投票の選択肢が1つで怒られる', async () => { - const res = await api('/notes/create', { + const res = await api('notes/create', { poll: { choices: ['Strawberry Pasta'], }, @@ -556,14 +557,14 @@ describe('Note', () => { }); test('投票できる', async () => { - const { body } = await api('/notes/create', { + const { body } = await api('notes/create', { text: 'test', poll: { choices: ['sakura', 'izumi', 'ako'], }, }, alice); - const res = await api('/notes/polls/vote', { + const res = await api('notes/polls/vote', { noteId: body.createdNote.id, choice: 1, }, alice); @@ -572,19 +573,19 @@ describe('Note', () => { }); test('複数投票できない', async () => { - const { body } = await api('/notes/create', { + const { body } = await api('notes/create', { text: 'test', poll: { choices: ['sakura', 'izumi', 'ako'], }, }, alice); - await api('/notes/polls/vote', { + await api('notes/polls/vote', { noteId: body.createdNote.id, choice: 0, }, alice); - const res = await api('/notes/polls/vote', { + const res = await api('notes/polls/vote', { noteId: body.createdNote.id, choice: 2, }, alice); @@ -593,7 +594,7 @@ describe('Note', () => { }); test('許可されている場合は複数投票できる', async () => { - const { body } = await api('/notes/create', { + const { body } = await api('notes/create', { text: 'test', poll: { choices: ['sakura', 'izumi', 'ako'], @@ -601,17 +602,17 @@ describe('Note', () => { }, }, alice); - await api('/notes/polls/vote', { + await api('notes/polls/vote', { noteId: body.createdNote.id, choice: 0, }, alice); - await api('/notes/polls/vote', { + await api('notes/polls/vote', { noteId: body.createdNote.id, choice: 1, }, alice); - const res = await api('/notes/polls/vote', { + const res = await api('notes/polls/vote', { noteId: body.createdNote.id, choice: 2, }, alice); @@ -620,7 +621,7 @@ describe('Note', () => { }); test('締め切られている場合は投票できない', async () => { - const { body } = await api('/notes/create', { + const { body } = await api('notes/create', { text: 'test', poll: { choices: ['sakura', 'izumi', 'ako'], @@ -630,7 +631,7 @@ describe('Note', () => { await new Promise(x => setTimeout(x, 2)); - const res = await api('/notes/polls/vote', { + const res = await api('notes/polls/vote', { noteId: body.createdNote.id, choice: 1, }, alice); @@ -649,7 +650,7 @@ describe('Note', () => { await new Promise(x => setTimeout(x, 2)); - const note1 = await api('/notes/create', { + const note1 = await api('notes/create', { text: 'hogetesthuge', }, alice); @@ -666,7 +667,7 @@ describe('Note', () => { assert.strictEqual(sensitive.status, 204); - const note2 = await api('/notes/create', { + const note2 = await api('notes/create', { text: 'hogetesthuge', }, alice); @@ -683,7 +684,7 @@ describe('Note', () => { assert.strictEqual(sensitive.status, 204); - const note2 = await api('/notes/create', { + const note2 = await api('notes/create', { text: 'hogeTesthuge', }, alice); @@ -702,7 +703,7 @@ describe('Note', () => { await new Promise(x => setTimeout(x, 2)); - const note1 = await api('/notes/create', { + const note1 = await api('notes/create', { text: 'hogetesthuge', }, alice); @@ -719,7 +720,7 @@ describe('Note', () => { assert.strictEqual(prohibited.status, 204); - const note2 = await api('/notes/create', { + const note2 = await api('notes/create', { text: 'hogetesthuge', }, alice); @@ -736,7 +737,7 @@ describe('Note', () => { assert.strictEqual(prohibited.status, 204); - const note2 = await api('/notes/create', { + const note2 = await api('notes/create', { text: 'hogeTesthuge', }, alice); @@ -755,7 +756,7 @@ describe('Note', () => { await new Promise(x => setTimeout(x, 2)); - const note1 = await api('/notes/create', { + const note1 = await api('notes/create', { text: 'hogetesthuge', }, tom); @@ -799,7 +800,7 @@ describe('Note', () => { await new Promise(x => setTimeout(x, 2)); - const note = await api('/notes/create', { + const note = await api('notes/create', { text: '@bob potentially annoying text', }, alice); @@ -853,10 +854,10 @@ describe('Note', () => { await new Promise(x => setTimeout(x, 2)); - const note = await api('/notes/create', { + const note = await api('notes/create', { text: 'potentially annoying text', visibility: 'specified', - visibleUserIds: [ bob.id ], + visibleUserIds: [bob.id], }, alice); assert.strictEqual(note.status, 400); @@ -909,10 +910,10 @@ describe('Note', () => { await new Promise(x => setTimeout(x, 2)); - const note = await api('/notes/create', { + const note = await api('notes/create', { text: '@bob potentially annoying text', visibility: 'specified', - visibleUserIds: [ bob.id ], + visibleUserIds: [bob.id], }, alice); assert.strictEqual(note.status, 200); diff --git a/packages/backend/test/e2e/renote-mute.ts b/packages/backend/test/e2e/renote-mute.ts index 403de0cb8d81..9826068e4826 100644 --- a/packages/backend/test/e2e/renote-mute.ts +++ b/packages/backend/test/e2e/renote-mute.ts @@ -22,7 +22,7 @@ describe('Renote Mute', () => { }, 1000 * 60 * 2); test('ミュート作成', async () => { - const res = await api('/renote-mute/create', { + const res = await api('renote-mute/create', { userId: carol.id, }, alice); @@ -37,7 +37,7 @@ describe('Renote Mute', () => { // redisに追加されるのを待つ await sleep(100); - const res = await api('/notes/local-timeline', {}, alice); + const res = await api('notes/local-timeline', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); @@ -54,7 +54,7 @@ describe('Renote Mute', () => { // redisに追加されるのを待つ await sleep(100); - const res = await api('/notes/local-timeline', {}, alice); + const res = await api('notes/local-timeline', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); diff --git a/packages/backend/test/e2e/streaming.ts b/packages/backend/test/e2e/streaming.ts index 57ce73ba603b..edb930617fff 100644 --- a/packages/backend/test/e2e/streaming.ts +++ b/packages/backend/test/e2e/streaming.ts @@ -601,7 +601,7 @@ describe('Streaming', () => { // #10443 test('ミュートしているサーバのノートがリストTLに流れない', async () => { - await api('/i/update', { + await api('i/update', { mutedInstances: ['example.com'], }, chitose); @@ -618,7 +618,7 @@ describe('Streaming', () => { // #10443 test('ミュートしているサーバのノートに対するリプライがリストTLに流れない', async () => { - await api('/i/update', { + await api('i/update', { mutedInstances: ['example.com'], }, chitose); @@ -635,7 +635,7 @@ describe('Streaming', () => { // #10443 test('ミュートしているサーバのノートに対するリノートがリストTLに流れない', async () => { - await api('/i/update', { + await api('i/update', { mutedInstances: ['example.com'], }, chitose); diff --git a/packages/backend/test/e2e/thread-mute.ts b/packages/backend/test/e2e/thread-mute.ts index b4570cdef15c..53bb6eb765a6 100644 --- a/packages/backend/test/e2e/thread-mute.ts +++ b/packages/backend/test/e2e/thread-mute.ts @@ -24,12 +24,12 @@ describe('Note thread mute', () => { const bobNote = await post(bob, { text: '@alice @carol root note' }); const aliceReply = await post(alice, { replyId: bobNote.id, text: '@bob @carol child note' }); - await api('/notes/thread-muting/create', { noteId: bobNote.id }, alice); + await api('notes/thread-muting/create', { noteId: bobNote.id }, alice); const carolReply = await post(carol, { replyId: bobNote.id, text: '@bob @alice child note' }); const carolReplyWithoutMention = await post(carol, { replyId: aliceReply.id, text: 'child note' }); - const res = await api('/notes/mentions', {}, alice); + const res = await api('notes/mentions', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); @@ -40,15 +40,15 @@ describe('Note thread mute', () => { test('ミュートしているスレッドからメンションされても、hasUnreadMentions が true にならない', async () => { // 状態リセット - await api('/i/read-all-unread-notes', {}, alice); + await api('i/read-all-unread-notes', {}, alice); const bobNote = await post(bob, { text: '@alice @carol root note' }); - await api('/notes/thread-muting/create', { noteId: bobNote.id }, alice); + await api('notes/thread-muting/create', { noteId: bobNote.id }, alice); const carolReply = await post(carol, { replyId: bobNote.id, text: '@bob @alice child note' }); - const res = await api('/i', {}, alice); + const res = await api('i', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(res.body.hasUnreadMentions, false); @@ -56,11 +56,11 @@ describe('Note thread mute', () => { test('ミュートしているスレッドからメンションされても、ストリームに unreadMention イベントが流れてこない', () => new Promise(async done => { // 状態リセット - await api('/i/read-all-unread-notes', {}, alice); + await api('i/read-all-unread-notes', {}, alice); const bobNote = await post(bob, { text: '@alice @carol root note' }); - await api('/notes/thread-muting/create', { noteId: bobNote.id }, alice); + await api('notes/thread-muting/create', { noteId: bobNote.id }, alice); let fired = false; @@ -84,12 +84,12 @@ describe('Note thread mute', () => { const bobNote = await post(bob, { text: '@alice @carol root note' }); const aliceReply = await post(alice, { replyId: bobNote.id, text: '@bob @carol child note' }); - await api('/notes/thread-muting/create', { noteId: bobNote.id }, alice); + await api('notes/thread-muting/create', { noteId: bobNote.id }, alice); const carolReply = await post(carol, { replyId: bobNote.id, text: '@bob @alice child note' }); const carolReplyWithoutMention = await post(carol, { replyId: aliceReply.id, text: 'child note' }); - const res = await api('/i/notifications', {}, alice); + const res = await api('i/notifications', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(Array.isArray(res.body), true); diff --git a/packages/backend/test/e2e/timelines.ts b/packages/backend/test/e2e/timelines.ts index 0e71d707dd13..d413703edeee 100644 --- a/packages/backend/test/e2e/timelines.ts +++ b/packages/backend/test/e2e/timelines.ts @@ -26,7 +26,7 @@ describe('Timelines', () => { 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: any) => note.id === aliceNote.id), true); assert.strictEqual(res.body.find((note: any) => note.id === aliceNote.id).text, 'hi'); @@ -35,14 +35,14 @@ describe('Timelines', () => { test.concurrent('フォローしているユーザーのノートが含まれる', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const bobNote = await post(bob, { text: 'hi' }); const carolNote = await post(carol, { text: 'hi' }); 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: any) => note.id === bobNote.id), true); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false); @@ -51,14 +51,14 @@ describe('Timelines', () => { test.concurrent('フォローしているユーザーの visibility: followers なノートが含まれる', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const bobNote = await post(bob, { text: 'hi', visibility: 'followers' }); const carolNote = await post(carol, { text: 'hi' }); 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: any) => note.id === bobNote.id), true); assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'hi'); @@ -68,14 +68,14 @@ describe('Timelines', () => { test.concurrent('withReplies: false でフォローしているユーザーの他人への返信が含まれない', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const carolNote = await post(carol, { text: 'hi' }); const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); 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: any) => note.id === bobNote.id), false); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false); @@ -84,15 +84,15 @@ describe('Timelines', () => { test.concurrent('withReplies: true でフォローしているユーザーの他人への返信が含まれる', 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('following/create', { userId: bob.id }, alice); + await api('following/update', { userId: bob.id, withReplies: true }, alice); await sleep(1000); const carolNote = await post(carol, { text: 'hi' }); const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); 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: any) => note.id === bobNote.id), true); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false); @@ -101,15 +101,15 @@ describe('Timelines', () => { test.concurrent('withReplies: true でフォローしているユーザーの他人へのDM返信が含まれない', 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('following/create', { userId: bob.id }, alice); + await api('following/update', { userId: bob.id, withReplies: true }, alice); await sleep(1000); const carolNote = await post(carol, { text: 'hi' }); const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id, visibility: 'specified', visibleUserIds: [carolNote.id] }); 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: any) => note.id === bobNote.id), false); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false); @@ -118,15 +118,15 @@ describe('Timelines', () => { test.concurrent('withReplies: true でフォローしているユーザーの他人の visibility: followers な投稿への返信が含まれない', 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('following/create', { userId: bob.id }, alice); + await api('following/update', { userId: bob.id, withReplies: true }, alice); await sleep(1000); const carolNote = await post(carol, { text: 'hi', visibility: 'followers' }); const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); 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: any) => note.id === bobNote.id), false); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false); @@ -135,17 +135,17 @@ describe('Timelines', () => { test.concurrent('withReplies: true でフォローしているユーザーの行った別のフォローしているユーザーの visibility: followers な投稿への返信が含まれる', 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); - await api('/following/create', { userId: carol.id }, bob); - await api('/following/update', { userId: bob.id, withReplies: true }, alice); + 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 sleep(1000); const carolNote = await post(carol, { text: 'hi', visibility: 'followers' }); const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); 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: any) => note.id === bobNote.id), true); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true); @@ -155,16 +155,16 @@ describe('Timelines', () => { test.concurrent('withReplies: true でフォローしているユーザーの行った別のフォローしているユーザーの投稿への visibility: specified な返信が含まれない', 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); - await api('/following/update', { userId: bob.id, withReplies: true }, alice); + 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 sleep(1000); const carolNote = await post(carol, { text: 'hi' }); const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id, visibility: 'specified', visibleUserIds: [carolNote.id] }); 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: any) => note.id === bobNote.id), false); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true); @@ -173,14 +173,14 @@ describe('Timelines', () => { test.concurrent('withReplies: false でフォローしているユーザーのそのユーザー自身への返信が含まれる', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const bobNote1 = await post(bob, { text: 'hi' }); const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id }); 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: any) => note.id === bobNote1.id), true); assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true); @@ -189,14 +189,14 @@ describe('Timelines', () => { test.concurrent('withReplies: false でフォローしているユーザーからの自分への返信が含まれる', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const aliceNote = await post(alice, { text: 'hi' }); const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id }); 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: any) => note.id === aliceNote.id), true); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); @@ -210,7 +210,7 @@ describe('Timelines', () => { 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: any) => note.id === bobNote.id), false); assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true); @@ -219,14 +219,14 @@ describe('Timelines', () => { test.concurrent('フォローしているユーザーの他人の投稿のリノートが含まれる', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const carolNote = await post(carol, { text: 'hi' }); const bobNote = await post(bob, { renoteId: carolNote.id }); 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: any) => note.id === bobNote.id), true); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false); @@ -235,14 +235,14 @@ describe('Timelines', () => { test.concurrent('[withRenotes: false] フォローしているユーザーの他人の投稿のリノートが含まれない', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const carolNote = await post(carol, { text: 'hi' }); const bobNote = await post(bob, { renoteId: carolNote.id }); await waitForPushToTl(); - const res = await api('/notes/timeline', { + const res = await api('notes/timeline', { withRenotes: false, }, alice); @@ -253,14 +253,14 @@ describe('Timelines', () => { test.concurrent('[withRenotes: false] フォローしているユーザーの他人の投稿の引用が含まれる', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const carolNote = await post(carol, { text: 'hi' }); const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id }); await waitForPushToTl(); - const res = await api('/notes/timeline', { + const res = await api('notes/timeline', { withRenotes: false, }, alice); @@ -271,13 +271,13 @@ describe('Timelines', () => { test.concurrent('フォローしているユーザーの他人への visibility: specified なノートが含まれない', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [carol.id] }); 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: any) => note.id === bobNote.id), false); }); @@ -285,15 +285,15 @@ describe('Timelines', () => { test.concurrent('フォローしているユーザーが行ったミュートしているユーザーのリノートが含まれない', 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 api('following/create', { userId: bob.id }, alice); + await api('mute/create', { userId: carol.id }, alice); await sleep(1000); const carolNote = await post(carol, { text: 'hi' }); const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id }); 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: any) => note.id === bobNote.id), false); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false); @@ -302,16 +302,16 @@ describe('Timelines', () => { test.concurrent('withReplies: true でフォローしているユーザーが行ったミュートしているユーザーの投稿への返信が含まれない', 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 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 sleep(1000); const carolNote = await post(carol, { text: 'hi' }); const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); 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: any) => note.id === bobNote.id), false); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false); @@ -321,13 +321,13 @@ describe('Timelines', () => { const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]); await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' }); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); const bobNote = await post(bob, { text: 'hi' }); 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: any) => note.id === bobNote.id), true); }); @@ -336,13 +336,13 @@ describe('Timelines', () => { const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]); await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' }); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); const bobNote = await post(bob, { text: 'hi', visibility: 'home' }); 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: any) => note.id === bobNote.id), true); }); @@ -350,7 +350,7 @@ describe('Timelines', () => { test.concurrent('[withFiles: true] フォローしているユーザーのファイル付きノートのみ含まれる', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const [bobFile, carolFile] = await Promise.all([ uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png'), @@ -363,7 +363,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/notes/timeline', { limit: 100, withFiles: true }, alice); + const res = await api('notes/timeline', { limit: 100, withFiles: true }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), false); assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true); @@ -374,14 +374,14 @@ describe('Timelines', () => { test.concurrent('フォローしているユーザーのチャンネル投稿が含まれない', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - const channel = await api('/channels/create', { name: 'channel' }, bob).then(x => x.body); - await api('/following/create', { userId: bob.id }, alice); + const channel = await api('channels/create', { name: 'channel' }, bob).then(x => x.body); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const bobNote = await post(bob, { text: 'hi', channelId: channel.id }); 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: any) => note.id === bobNote.id), false); }); @@ -393,7 +393,7 @@ describe('Timelines', () => { 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: any) => note.id === aliceNote.id), true); assert.strictEqual(res.body.find((note: any) => note.id === aliceNote.id).text, 'hi'); @@ -402,13 +402,13 @@ describe('Timelines', () => { test.concurrent('フォローしているユーザーの自身を visibleUserIds に指定した visibility: specified なノートが含まれる', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [alice.id] }); 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: any) => note.id === bobNote.id), true); assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'hi'); @@ -421,7 +421,7 @@ describe('Timelines', () => { 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: any) => note.id === bobNote.id), false); }); @@ -429,13 +429,13 @@ describe('Timelines', () => { test.concurrent('フォローしているユーザーの自身を visibleUserIds に指定していない visibility: specified なノートが含まれない', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [carol.id] }); 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: any) => note.id === bobNote.id), false); }); @@ -448,7 +448,7 @@ describe('Timelines', () => { 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: any) => note.id === aliceNote.id), true); assert.strictEqual(res.body.find((note: any) => note.id === aliceNote.id).text, 'ok'); @@ -463,7 +463,7 @@ describe('Timelines', () => { 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: any) => note.id === bobNote.id), true); assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'ok'); @@ -479,7 +479,7 @@ describe('Timelines', () => { 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: any) => note.id === bobNote.id), false); }); @@ -494,7 +494,7 @@ describe('Timelines', () => { 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: any) => note.id === bobNote.id), true); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false); @@ -508,7 +508,7 @@ describe('Timelines', () => { 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: any) => note.id === bobNote.id), false); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true); @@ -522,7 +522,7 @@ describe('Timelines', () => { 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: any) => note.id === bobNote1.id), true); assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true); @@ -531,12 +531,12 @@ describe('Timelines', () => { test.concurrent('チャンネル投稿が含まれない', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - const channel = await api('/channels/create', { name: 'channel' }, bob).then(x => x.body); + 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('/notes/local-timeline', { limit: 100 }, alice); + const res = await api('notes/local-timeline', { limit: 100 }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); }); @@ -548,7 +548,7 @@ describe('Timelines', () => { 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: any) => note.id === bobNote.id), false); }); @@ -557,14 +557,14 @@ describe('Timelines', () => { test.concurrent('フォローしているユーザーの visibility: home なノートが含まれない', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await api('/following/create', { userId: carol.id }, alice); + await api('following/create', { userId: carol.id }, alice); await sleep(1000); const carolNote = await post(carol, { text: 'hi', visibility: 'home' }); const bobNote = await post(bob, { text: 'hi' }); 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: any) => note.id === bobNote.id), true); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false); @@ -573,14 +573,14 @@ describe('Timelines', () => { test.concurrent('ミュートしているユーザーのノートが含まれない', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await api('/mute/create', { userId: carol.id }, alice); + await api('mute/create', { userId: carol.id }, alice); await sleep(1000); const carolNote = await post(carol, { text: 'hi' }); const bobNote = await post(bob, { text: 'hi' }); 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: any) => note.id === bobNote.id), true); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false); @@ -589,15 +589,15 @@ describe('Timelines', () => { test.concurrent('フォローしているユーザーが行ったミュートしているユーザーのリノートが含まれない', 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 api('following/create', { userId: bob.id }, alice); + await api('mute/create', { userId: carol.id }, alice); await sleep(1000); const carolNote = await post(carol, { text: 'hi' }); const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id }); 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: any) => note.id === bobNote.id), false); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false); @@ -606,16 +606,16 @@ describe('Timelines', () => { test.concurrent('withReplies: true でフォローしているユーザーが行ったミュートしているユーザーの投稿への返信が含まれない', 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 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 sleep(1000); const carolNote = await post(carol, { text: 'hi' }); const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); 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: any) => note.id === bobNote.id), false); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false); @@ -624,14 +624,14 @@ describe('Timelines', () => { test.concurrent('withReplies: false でフォローしているユーザーからの自分への返信が含まれる', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const aliceNote = await post(alice, { text: 'hi' }); const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id }); 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: any) => note.id === aliceNote.id), true); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); @@ -645,7 +645,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/notes/local-timeline', { limit: 100, withReplies: true }, alice); + const res = await api('notes/local-timeline', { limit: 100, withReplies: true }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); }); @@ -659,7 +659,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/notes/local-timeline', { limit: 100, withFiles: true }, alice); + const res = await api('notes/local-timeline', { limit: 100, withFiles: true }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), false); assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true); @@ -674,7 +674,7 @@ describe('Timelines', () => { 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: any) => note.id === bobNote.id), true); }); @@ -686,7 +686,7 @@ describe('Timelines', () => { 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: any) => note.id === bobNote.id), false); }); @@ -694,13 +694,13 @@ describe('Timelines', () => { test.concurrent('フォローしているローカルユーザーの visibility: home なノートが含まれる', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const bobNote = await post(bob, { text: 'hi', visibility: 'home' }); 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: any) => note.id === bobNote.id), true); }); @@ -708,14 +708,14 @@ describe('Timelines', () => { test.concurrent('withReplies: false でフォローしているユーザーからの自分への返信が含まれる', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const aliceNote = await post(alice, { text: 'hi' }); const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id }); 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: any) => note.id === aliceNote.id), true); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); @@ -729,7 +729,7 @@ describe('Timelines', () => { 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: any) => note.id === bobNote.id), false); assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true); @@ -742,7 +742,7 @@ describe('Timelines', () => { 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: any) => note.id === bobNote.id), false); }); @@ -751,13 +751,13 @@ describe('Timelines', () => { const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]); await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' }); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); const bobNote = await post(bob, { text: 'hi' }); 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: any) => note.id === bobNote.id), true); }); @@ -766,13 +766,13 @@ describe('Timelines', () => { const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]); await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' }); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); const bobNote = await post(bob, { text: 'hi', visibility: 'home' }); 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: any) => note.id === bobNote.id), true); }); @@ -785,7 +785,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/notes/hybrid-timeline', { limit: 100, withReplies: true }, alice); + const res = await api('notes/hybrid-timeline', { limit: 100, withReplies: true }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); }); @@ -799,7 +799,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/notes/hybrid-timeline', { limit: 100, withFiles: true }, alice); + const res = await api('notes/hybrid-timeline', { limit: 100, withFiles: true }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), false); assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true); @@ -810,14 +810,14 @@ describe('Timelines', () => { test.concurrent('リスインしているフォローしていないユーザーのノートが含まれる', 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: 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 sleep(1000); const bobNote = await post(bob, { text: 'hi' }); await waitForPushToTl(); - const res = await api('/notes/user-list-timeline', { listId: list.id }, alice); + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); }); @@ -825,14 +825,14 @@ describe('Timelines', () => { test.concurrent('リスインしているフォローしていないユーザーの visibility: home なノートが含まれる', 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: 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 sleep(1000); const bobNote = await post(bob, { text: 'hi', visibility: 'home' }); await waitForPushToTl(); - const res = await api('/notes/user-list-timeline', { listId: list.id }, alice); + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); }); @@ -840,14 +840,14 @@ describe('Timelines', () => { test.concurrent('リスインしているフォローしていないユーザーの visibility: followers なノートが含まれない', 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: 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 sleep(1000); const bobNote = await post(bob, { text: 'hi', visibility: 'followers' }); await waitForPushToTl(); - const res = await api('/notes/user-list-timeline', { listId: list.id }, alice); + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); }); @@ -855,15 +855,15 @@ describe('Timelines', () => { test.concurrent('リスインしているフォローしていないユーザーの他人への返信が含まれない', 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); + 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 sleep(1000); const carolNote = await post(carol, { text: 'hi' }); const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); await waitForPushToTl(); - const res = await api('/notes/user-list-timeline', { listId: list.id }, alice); + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); }); @@ -871,15 +871,15 @@ describe('Timelines', () => { test.concurrent('リスインしているフォローしていないユーザーのユーザー自身への返信が含まれる', 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: 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 sleep(1000); const bobNote1 = await post(bob, { text: 'hi' }); const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id }); await waitForPushToTl(); - const res = await api('/notes/user-list-timeline', { listId: list.id }, alice); + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), true); assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true); @@ -888,15 +888,15 @@ describe('Timelines', () => { test.concurrent('withReplies: false でリスインしているフォローしていないユーザーからの自分への返信が含まれる', 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: 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 sleep(1000); const aliceNote = await post(alice, { text: 'hi' }); const bobNote = await post(bob, { text: 'hi', replyId: aliceNote.id }); await waitForPushToTl(); - const res = await api('/notes/user-list-timeline', { listId: list.id, withReplies: false }, alice); + const res = await api('notes/user-list-timeline', { listId: list.id, withReplies: false }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); }); @@ -904,16 +904,16 @@ describe('Timelines', () => { test.concurrent('withReplies: true でリスインしているフォローしていないユーザーの他人への返信が含まれる', 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 api('/users/lists/update-membership', { listId: list.id, userId: bob.id, withReplies: true }, 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: true }, alice); await sleep(1000); const carolNote = await post(carol, { text: 'hi' }); const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id }); await waitForPushToTl(); - const res = await api('/notes/user-list-timeline', { listId: list.id }, alice); + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); }); @@ -921,15 +921,15 @@ describe('Timelines', () => { test.concurrent('リスインしているフォローしているユーザーの visibility: home なノートが含まれる', async () => { 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 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 sleep(1000); const bobNote = await post(bob, { text: 'hi', visibility: 'home' }); await waitForPushToTl(); - const res = await api('/notes/user-list-timeline', { listId: list.id }, alice); + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); }); @@ -937,15 +937,15 @@ describe('Timelines', () => { test.concurrent('リスインしているフォローしているユーザーの visibility: followers なノートが含まれる', async () => { 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 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 sleep(1000); const bobNote = await post(bob, { text: 'hi', visibility: 'followers' }); await waitForPushToTl(); - const res = await api('/notes/user-list-timeline', { listId: list.id }, alice); + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'hi'); @@ -954,14 +954,14 @@ describe('Timelines', () => { test.concurrent('リスインしている自分の visibility: followers なノートが含まれる', async () => { const [alice] = 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); + 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 sleep(1000); const aliceNote = await post(alice, { text: 'hi', visibility: 'followers' }); await waitForPushToTl(); - const res = await api('/notes/user-list-timeline', { listId: list.id }, alice); + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true); assert.strictEqual(res.body.find((note: any) => note.id === aliceNote.id).text, 'hi'); @@ -970,15 +970,15 @@ describe('Timelines', () => { test.concurrent('リスインしているユーザーのチャンネルノートが含まれない', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - 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); + 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 sleep(1000); const bobNote = await post(bob, { text: 'hi', channelId: channel.id }); await waitForPushToTl(); - const res = await api('/notes/user-list-timeline', { listId: list.id }, alice); + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); }); @@ -986,15 +986,15 @@ describe('Timelines', () => { test.concurrent('[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: 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); 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('/notes/user-list-timeline', { listId: list.id, withFiles: true }, alice); + const res = await api('notes/user-list-timeline', { listId: list.id, withFiles: true }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), false); assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true); @@ -1003,14 +1003,14 @@ describe('Timelines', () => { test.concurrent('リスインしているユーザーの自身宛ての visibility: specified なノートが含まれる', 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: 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 sleep(1000); const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [alice.id] }); await waitForPushToTl(); - const res = await api('/notes/user-list-timeline', { listId: list.id }, alice); + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'hi'); @@ -1019,15 +1019,15 @@ describe('Timelines', () => { test.concurrent('リスインしているユーザーの自身宛てではない visibility: specified なノートが含まれない', 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 api('/users/lists/push', { listId: list.id, userId: carol.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 api('users/lists/push', { listId: list.id, userId: carol.id }, alice); await sleep(1000); const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [carol.id] }); await waitForPushToTl(); - const res = await api('/notes/user-list-timeline', { listId: list.id }, alice); + const res = await api('notes/user-list-timeline', { listId: list.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); }); @@ -1041,7 +1041,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/users/notes', { userId: bob.id }, alice); + const res = await api('users/notes', { userId: bob.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); }); @@ -1053,7 +1053,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/users/notes', { userId: bob.id }, alice); + const res = await api('users/notes', { userId: bob.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); }); @@ -1061,13 +1061,13 @@ describe('Timelines', () => { test.concurrent('フォローしているユーザーの visibility: followers なノートが含まれる', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - await api('/following/create', { userId: bob.id }, alice); + await api('following/create', { userId: bob.id }, alice); await sleep(1000); const bobNote = await post(bob, { text: 'hi', visibility: 'followers' }); await waitForPushToTl(); - const res = await api('/users/notes', { userId: bob.id }, alice); + const res = await api('users/notes', { userId: bob.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'hi'); @@ -1080,7 +1080,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/users/notes', { userId: alice.id }, alice); + const res = await api('users/notes', { userId: alice.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true); assert.strictEqual(res.body.find((note: any) => note.id === aliceNote.id).text, 'hi'); @@ -1089,12 +1089,12 @@ describe('Timelines', () => { test.concurrent('チャンネル投稿が含まれない', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - const channel = await api('/channels/create', { name: 'channel' }, bob).then(x => x.body); + 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); + const res = await api('users/notes', { userId: bob.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); }); @@ -1108,7 +1108,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/users/notes', { userId: bob.id }, alice); + const res = await api('users/notes', { userId: bob.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), true); assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), false); @@ -1123,7 +1123,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/users/notes', { userId: bob.id, withReplies: true }, alice); + const res = await api('users/notes', { userId: bob.id, withReplies: true }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), true); assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true); @@ -1138,7 +1138,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/users/notes', { userId: bob.id, withReplies: true }, alice); + const res = await api('users/notes', { userId: bob.id, withReplies: true }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), true); assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), false); @@ -1153,7 +1153,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/users/notes', { userId: bob.id, withFiles: true }, alice); + const res = await api('users/notes', { userId: bob.id, withFiles: true }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), false); assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true); @@ -1162,12 +1162,12 @@ describe('Timelines', () => { test.concurrent('[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 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, withChannelNotes: true }, alice); + const res = await api('users/notes', { userId: bob.id, withChannelNotes: true }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); }); @@ -1175,12 +1175,12 @@ describe('Timelines', () => { test.concurrent('[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 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); + const res = await api('users/notes', { userId: bob.id, withChannelNotes: true }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); }); @@ -1188,12 +1188,12 @@ describe('Timelines', () => { test.concurrent('[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 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); + const res = await api('users/notes', { userId: bob.id, withChannelNotes: true }, bob); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true); }); @@ -1201,14 +1201,14 @@ describe('Timelines', () => { test.concurrent('ミュートしているユーザーに関連する投稿が含まれない', async () => { const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]); - await api('/mute/create', { userId: carol.id }, alice); + await api('mute/create', { userId: carol.id }, alice); await sleep(1000); 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); + const res = await api('users/notes', { userId: bob.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); }); @@ -1216,7 +1216,7 @@ describe('Timelines', () => { test.concurrent('ミュートしていても userId に指定したユーザーの投稿が含まれる', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - await api('/mute/create', { userId: bob.id }, alice); + await api('mute/create', { userId: bob.id }, alice); await sleep(1000); const bobNote1 = await post(bob, { text: 'hi' }); const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id }); @@ -1224,7 +1224,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/users/notes', { userId: bob.id }, alice); + const res = await api('users/notes', { userId: bob.id }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), true); assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true); @@ -1238,7 +1238,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/users/notes', { userId: alice.id, withReplies: true }, alice); + const res = await api('users/notes', { userId: alice.id, withReplies: true }, alice); assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true); }); @@ -1250,7 +1250,7 @@ describe('Timelines', () => { await waitForPushToTl(); - const res = await api('/users/notes', { userId: bob.id, withReplies: true }, alice); + const res = await api('users/notes', { userId: bob.id, withReplies: true }, alice); assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); }); diff --git a/packages/backend/test/e2e/user-notes.ts b/packages/backend/test/e2e/user-notes.ts index 6897cf08c626..331e053935f8 100644 --- a/packages/backend/test/e2e/user-notes.ts +++ b/packages/backend/test/e2e/user-notes.ts @@ -11,9 +11,9 @@ import type * as misskey from 'misskey-js'; describe('users/notes', () => { let alice: misskey.entities.SignupResponse; - let jpgNote: any; - let pngNote: any; - let jpgPngNote: any; + let jpgNote: misskey.entities.Note; + let pngNote: misskey.entities.Note; + let jpgPngNote: misskey.entities.Note; beforeAll(async () => { alice = await signup({ username: 'alice' }); @@ -31,7 +31,7 @@ describe('users/notes', () => { }, 1000 * 60 * 2); test('withFiles', async () => { - const res = await api('/users/notes', { + const res = await api('users/notes', { userId: alice.id, withFiles: true, }, alice); diff --git a/packages/backend/test/e2e/users.ts b/packages/backend/test/e2e/users.ts index 3cf2a5dee186..3458e06384f2 100644 --- a/packages/backend/test/e2e/users.ts +++ b/packages/backend/test/e2e/users.ts @@ -8,7 +8,7 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import { inspect } from 'node:util'; import { DEFAULT_POLICIES } from '@/core/RoleService.js'; -import { api, page, post, role, signup, successfulApiCall, uploadFile } from '../utils.js'; +import { api, post, role, signup, successfulApiCall, uploadFile } from '../utils.js'; import type * as misskey from 'misskey-js'; describe('ユーザー', () => { @@ -24,31 +24,12 @@ describe('ユーザー', () => { }, {}); }; - // BUG misskey-jsとjson-schemaと実際に返ってくるデータが全部違う - type UserLite = misskey.entities.UserLite & { - badgeRoles: any[], - }; - - type UserDetailedNotMe = UserLite & - misskey.entities.UserDetailed & { - roles: any[], - }; - - type MeDetailed = UserDetailedNotMe & - misskey.entities.MeDetailed & { - achievements: object[], - loggedInDays: number, - policies: object, - }; - - type User = MeDetailed & { token: string }; - - const show = async (id: string, me = root): Promise => { - return successfulApiCall({ endpoint: 'users/show', parameters: { userId: id }, user: me }) as any; + const show = async (id: string, me = root): Promise => { + return successfulApiCall({ endpoint: 'users/show', parameters: { userId: id }, user: me }); }; // UserLiteのキーが過不足なく入っている? - const userLite = (user: User): Partial => { + const userLite = (user: misskey.entities.UserLite): Partial => { return stripUndefined({ id: user.id, name: user.name, @@ -71,7 +52,7 @@ describe('ユーザー', () => { }; // UserDetailedNotMeのキーが過不足なく入っている? - const userDetailedNotMe = (user: User): Partial => { + const userDetailedNotMe = (user: misskey.entities.SignupResponse): Partial => { return stripUndefined({ ...userLite(user), url: user.url, @@ -111,7 +92,7 @@ describe('ユーザー', () => { }; // Relations関連のキーが過不足なく入っている? - const userDetailedNotMeWithRelations = (user: User): Partial => { + const userDetailedNotMeWithRelations = (user: misskey.entities.SignupResponse): Partial => { return stripUndefined({ ...userDetailedNotMe(user), isFollowing: user.isFollowing ?? false, @@ -128,7 +109,7 @@ describe('ユーザー', () => { }; // MeDetailedのキーが過不足なく入っている? - const meDetailed = (user: User, security = false): Partial => { + const meDetailed = (user: misskey.entities.SignupResponse, security = false): Partial => { return stripUndefined({ ...userDetailedNotMe(user), avatarId: user.avatarId, @@ -159,6 +140,7 @@ describe('ユーザー', () => { mutedWords: user.mutedWords, hardMutedWords: user.hardMutedWords, mutedInstances: user.mutedInstances, + // @ts-expect-error 後方互換性 mutingNotificationTypes: user.mutingNotificationTypes, notificationRecieveConfig: user.notificationRecieveConfig, emailNotificationTypes: user.emailNotificationTypes, @@ -173,61 +155,53 @@ describe('ユーザー', () => { }); }; - let root: User; - let alice: User; + let root: misskey.entities.SignupResponse; + let alice: misskey.entities.SignupResponse; let aliceNote: misskey.entities.Note; - let alicePage: misskey.entities.Page; - let aliceList: misskey.entities.UserList; - - let bob: User; - let bobNote: misskey.entities.Note; - - let carol: User; - let dave: User; - let ellen: User; - let frank: User; - - let usersReplying: User[]; - - let userNoNote: User; - let userNotExplorable: User; - let userLocking: User; - let userAdmin: User; - let roleAdmin: any; - let userModerator: User; - let roleModerator: any; - let userRolePublic: User; - let rolePublic: any; - let userRoleBadge: User; - let roleBadge: any; - let userSilenced: User; - let roleSilenced: any; - let userSuspended: User; - let userDeletedBySelf: User; - let userDeletedByAdmin: User; - let userFollowingAlice: User; - let userFollowedByAlice: User; - let userBlockingAlice: User; - let userBlockedByAlice: User; - let userMutingAlice: User; - let userMutedByAlice: User; - let userRnMutingAlice: User; - let userRnMutedByAlice: User; - let userFollowRequesting: User; - let userFollowRequested: User; + + let bob: misskey.entities.SignupResponse; + + // NOTE: これがないと落ちる(bob の updatedAt が null になってしまうため?) + let bobNote: misskey.entities.Note; // eslint-disable-line @typescript-eslint/no-unused-vars + + let carol: misskey.entities.SignupResponse; + + let usersReplying: misskey.entities.SignupResponse[]; + + let userNoNote: misskey.entities.SignupResponse; + let userNotExplorable: misskey.entities.SignupResponse; + let userLocking: misskey.entities.SignupResponse; + let userAdmin: misskey.entities.SignupResponse; + let roleAdmin: misskey.entities.Role; + let userModerator: misskey.entities.SignupResponse; + let roleModerator: misskey.entities.Role; + let userRolePublic: misskey.entities.SignupResponse; + let rolePublic: misskey.entities.Role; + let userRoleBadge: misskey.entities.SignupResponse; + let roleBadge: misskey.entities.Role; + let userSilenced: misskey.entities.SignupResponse; + let roleSilenced: misskey.entities.Role; + let userSuspended: misskey.entities.SignupResponse; + let userDeletedBySelf: misskey.entities.SignupResponse; + let userDeletedByAdmin: misskey.entities.SignupResponse; + let userFollowingAlice: misskey.entities.SignupResponse; + let userFollowedByAlice: misskey.entities.SignupResponse; + let userBlockingAlice: misskey.entities.SignupResponse; + let userBlockedByAlice: misskey.entities.SignupResponse; + let userMutingAlice: misskey.entities.SignupResponse; + let userMutedByAlice: misskey.entities.SignupResponse; + let userRnMutingAlice: misskey.entities.SignupResponse; + let userRnMutedByAlice: misskey.entities.SignupResponse; + let userFollowRequesting: misskey.entities.SignupResponse; + let userFollowRequested: misskey.entities.SignupResponse; beforeAll(async () => { root = await signup({ username: 'root' }); alice = await signup({ username: 'alice' }); - aliceNote = await post(alice, { text: 'test' }) as any; - alicePage = await page(alice); - aliceList = (await api('users/list/create', { name: 'aliceList' }, alice)).body; + aliceNote = await post(alice, { text: 'test' }); bob = await signup({ username: 'bob' }); - bobNote = await post(bob, { text: 'test' }) as any; + bobNote = await post(bob, { text: 'test' }); carol = await signup({ username: 'carol' }); - dave = await signup({ username: 'dave' }); - ellen = await signup({ username: 'ellen' }); - frank = await signup({ username: 'frank' }); // @alice -> @replyingへのリプライ。Promise.allで一気に作るとtimeoutしてしまうのでreduceで一つ一つawaitする usersReplying = await [...Array(10)].map((_, i) => i).reduce(async (acc, i) => { @@ -238,7 +212,7 @@ describe('ユーザー', () => { } return (await acc).concat(u); - }, Promise.resolve([] as User[])); + }, Promise.resolve([] as misskey.entities.SignupResponse[])); userNoNote = await signup({ username: 'userNoNote' }); userNotExplorable = await signup({ username: 'userNotExplorable' }); @@ -306,7 +280,7 @@ describe('ユーザー', () => { beforeEach(async () => { alice = { ...alice, - ...await successfulApiCall({ endpoint: 'i', parameters: {}, user: alice }) as any, + ...await successfulApiCall({ endpoint: 'i', parameters: {}, user: alice }), }; aliceNote = await successfulApiCall({ endpoint: 'notes/show', parameters: { noteId: aliceNote.id }, user: alice }); }); @@ -319,7 +293,7 @@ describe('ユーザー', () => { endpoint: 'signup', parameters: { username: 'zoe', password: 'password' }, user: undefined, - }) as unknown as User; // BUG MeDetailedに足りないキーがある + }) as unknown as misskey.entities.SignupResponse; // BUG MeDetailedに足りないキーがある // signupの時はtokenが含まれる特別なMeDetailedが返ってくる assert.match(response.token, /[a-zA-Z0-9]{16}/); @@ -329,7 +303,7 @@ describe('ユーザー', () => { assert.strictEqual(response.name, null); assert.strictEqual(response.username, 'zoe'); assert.strictEqual(response.host, null); - assert.match(response.avatarUrl, /^[-a-zA-Z0-9@:%._\+~#&?=\/]+$/); + response.avatarUrl && assert.match(response.avatarUrl, /^[-a-zA-Z0-9@:%._\+~#&?=\/]+$/); assert.strictEqual(response.avatarBlurhash, null); assert.deepStrictEqual(response.avatarDecorations, []); assert.strictEqual(response.isBot, false); @@ -401,6 +375,7 @@ describe('ユーザー', () => { assert.deepStrictEqual(response.unreadAnnouncements, []); assert.deepStrictEqual(response.mutedWords, []); assert.deepStrictEqual(response.mutedInstances, []); + // @ts-expect-error 後方互換のため assert.deepStrictEqual(response.mutingNotificationTypes, []); assert.deepStrictEqual(response.notificationRecieveConfig, {}); assert.deepStrictEqual(response.emailNotificationTypes, ['follow', 'receiveFollowRequest']); @@ -430,66 +405,66 @@ describe('ユーザー', () => { //#region 自分の情報の更新(i/update) test.each([ - { parameters: (): object => ({ name: null }) }, - { parameters: (): object => ({ name: 'x'.repeat(50) }) }, - { parameters: (): object => ({ name: 'x' }) }, - { parameters: (): object => ({ name: 'My name' }) }, - { parameters: (): object => ({ description: null }) }, - { parameters: (): object => ({ description: 'x'.repeat(1500) }) }, - { parameters: (): object => ({ description: 'x' }) }, - { parameters: (): object => ({ description: 'My description' }) }, - { parameters: (): object => ({ location: null }) }, - { parameters: (): object => ({ location: 'x'.repeat(50) }) }, - { parameters: (): object => ({ location: 'x' }) }, - { parameters: (): object => ({ location: 'My location' }) }, - { parameters: (): object => ({ birthday: '0000-00-00' }) }, - { parameters: (): object => ({ birthday: '9999-99-99' }) }, - { parameters: (): object => ({ lang: 'en-US' }) }, - { parameters: (): object => ({ fields: [] }) }, - { parameters: (): object => ({ fields: [{ name: 'x', value: 'x' }] }) }, - { parameters: (): object => ({ fields: [{ name: 'x'.repeat(3000), value: 'x'.repeat(3000) }] }) }, // BUG? fieldには制限がない - { parameters: (): object => ({ fields: Array(16).fill({ name: 'x', value: 'y' }) }) }, - { parameters: (): object => ({ isLocked: true }) }, - { parameters: (): object => ({ isLocked: false }) }, - { parameters: (): object => ({ isExplorable: false }) }, - { parameters: (): object => ({ isExplorable: true }) }, - { parameters: (): object => ({ hideOnlineStatus: true }) }, - { parameters: (): object => ({ hideOnlineStatus: false }) }, - { parameters: (): object => ({ publicReactions: false }) }, - { parameters: (): object => ({ publicReactions: true }) }, - { parameters: (): object => ({ autoAcceptFollowed: true }) }, - { parameters: (): object => ({ autoAcceptFollowed: false }) }, - { parameters: (): object => ({ noCrawle: true }) }, - { parameters: (): object => ({ noCrawle: false }) }, - { parameters: (): object => ({ preventAiLearning: false }) }, - { parameters: (): object => ({ preventAiLearning: true }) }, - { parameters: (): object => ({ isBot: true }) }, - { parameters: (): object => ({ isBot: false }) }, - { parameters: (): object => ({ isCat: true }) }, - { parameters: (): object => ({ isCat: false }) }, - { parameters: (): object => ({ injectFeaturedNote: true }) }, - { parameters: (): object => ({ injectFeaturedNote: false }) }, - { parameters: (): object => ({ receiveAnnouncementEmail: true }) }, - { parameters: (): object => ({ receiveAnnouncementEmail: false }) }, - { parameters: (): object => ({ alwaysMarkNsfw: true }) }, - { parameters: (): object => ({ alwaysMarkNsfw: false }) }, - { parameters: (): object => ({ autoSensitive: true }) }, - { parameters: (): object => ({ autoSensitive: false }) }, - { parameters: (): object => ({ followingVisibility: 'private' }) }, - { parameters: (): object => ({ followingVisibility: 'followers' }) }, - { parameters: (): object => ({ followingVisibility: 'public' }) }, - { parameters: (): object => ({ followersVisibility: 'private' }) }, - { parameters: (): object => ({ followersVisibility: 'followers' }) }, - { parameters: (): object => ({ followersVisibility: 'public' }) }, - { parameters: (): object => ({ mutedWords: Array(19).fill(['xxxxx']) }) }, - { parameters: (): object => ({ mutedWords: [['x'.repeat(194)]] }) }, - { parameters: (): object => ({ mutedWords: [] }) }, - { parameters: (): object => ({ mutedInstances: ['xxxx.xxxxx'] }) }, - { parameters: (): object => ({ mutedInstances: [] }) }, - { parameters: (): object => ({ notificationRecieveConfig: { mention: { type: 'following' } } }) }, - { parameters: (): object => ({ notificationRecieveConfig: {} }) }, - { parameters: (): object => ({ emailNotificationTypes: ['mention', 'reply', 'quote', 'follow', 'receiveFollowRequest'] }) }, - { parameters: (): object => ({ emailNotificationTypes: [] }) }, + { parameters: () => ({ name: null }) }, + { parameters: () => ({ name: 'x'.repeat(50) }) }, + { parameters: () => ({ name: 'x' }) }, + { parameters: () => ({ name: 'My name' }) }, + { parameters: () => ({ description: null }) }, + { parameters: () => ({ description: 'x'.repeat(1500) }) }, + { parameters: () => ({ description: 'x' }) }, + { parameters: () => ({ description: 'My description' }) }, + { parameters: () => ({ location: null }) }, + { parameters: () => ({ location: 'x'.repeat(50) }) }, + { parameters: () => ({ location: 'x' }) }, + { parameters: () => ({ location: 'My location' }) }, + { parameters: () => ({ birthday: '0000-00-00' }) }, + { parameters: () => ({ birthday: '9999-99-99' }) }, + { parameters: () => ({ lang: 'en-US' as const }) }, + { parameters: () => ({ fields: [] }) }, + { parameters: () => ({ fields: [{ name: 'x', value: 'x' }] }) }, + { parameters: () => ({ fields: [{ name: 'x'.repeat(3000), value: 'x'.repeat(3000) }] }) }, // BUG? fieldには制限がない + { parameters: () => ({ fields: Array(16).fill({ name: 'x', value: 'y' }) }) }, + { parameters: () => ({ isLocked: true }) }, + { parameters: () => ({ isLocked: false }) }, + { parameters: () => ({ isExplorable: false }) }, + { parameters: () => ({ isExplorable: true }) }, + { parameters: () => ({ hideOnlineStatus: true }) }, + { parameters: () => ({ hideOnlineStatus: false }) }, + { parameters: () => ({ publicReactions: false }) }, + { parameters: () => ({ publicReactions: true }) }, + { parameters: () => ({ autoAcceptFollowed: true }) }, + { parameters: () => ({ autoAcceptFollowed: false }) }, + { parameters: () => ({ noCrawle: true }) }, + { parameters: () => ({ noCrawle: false }) }, + { parameters: () => ({ preventAiLearning: false }) }, + { parameters: () => ({ preventAiLearning: true }) }, + { parameters: () => ({ isBot: true }) }, + { parameters: () => ({ isBot: false }) }, + { parameters: () => ({ isCat: true }) }, + { parameters: () => ({ isCat: false }) }, + { parameters: () => ({ injectFeaturedNote: true }) }, + { parameters: () => ({ injectFeaturedNote: false }) }, + { parameters: () => ({ receiveAnnouncementEmail: true }) }, + { parameters: () => ({ receiveAnnouncementEmail: false }) }, + { parameters: () => ({ alwaysMarkNsfw: true }) }, + { parameters: () => ({ alwaysMarkNsfw: false }) }, + { parameters: () => ({ autoSensitive: true }) }, + { parameters: () => ({ autoSensitive: false }) }, + { parameters: () => ({ followingVisibility: 'private' as const }) }, + { parameters: () => ({ followingVisibility: 'followers' as const }) }, + { parameters: () => ({ followingVisibility: 'public' as const }) }, + { parameters: () => ({ followersVisibility: 'private' as const }) }, + { parameters: () => ({ followersVisibility: 'followers' as const }) }, + { parameters: () => ({ followersVisibility: 'public' as const }) }, + { parameters: () => ({ mutedWords: Array(19).fill(['xxxxx']) }) }, + { parameters: () => ({ mutedWords: [['x'.repeat(194)]] }) }, + { parameters: () => ({ mutedWords: [] }) }, + { parameters: () => ({ mutedInstances: ['xxxx.xxxxx'] }) }, + { parameters: () => ({ mutedInstances: [] }) }, + { parameters: () => ({ notificationRecieveConfig: { mention: { type: 'following' } } }) }, + { parameters: () => ({ notificationRecieveConfig: {} }) }, + { parameters: () => ({ emailNotificationTypes: ['mention', 'reply', 'quote', 'follow', 'receiveFollowRequest'] }) }, + { parameters: () => ({ emailNotificationTypes: [] }) }, ] as const)('を書き換えることができる($#)', async ({ parameters }) => { const response = await successfulApiCall({ endpoint: 'i/update', parameters: parameters(), user: alice }); const expected = { ...meDetailed(alice, true), ...parameters() }; @@ -498,13 +473,13 @@ describe('ユーザー', () => { test('を書き換えることができる(Avatar)', async () => { const aliceFile = (await uploadFile(alice)).body; - const parameters = { avatarId: aliceFile.id }; + const parameters = { avatarId: aliceFile!.id }; const response = await successfulApiCall({ endpoint: 'i/update', parameters: parameters, user: alice }); assert.match(response.avatarUrl ?? '.', /^[-a-zA-Z0-9@:%._\+~#&?=\/]+$/); assert.match(response.avatarBlurhash ?? '.', /[ -~]{54}/); const expected = { ...meDetailed(alice, true), - avatarId: aliceFile.id, + avatarId: aliceFile!.id, avatarBlurhash: response.avatarBlurhash, avatarUrl: response.avatarUrl, }; @@ -523,13 +498,13 @@ describe('ユーザー', () => { test('を書き換えることができる(Banner)', async () => { const aliceFile = (await uploadFile(alice)).body; - const parameters = { bannerId: aliceFile.id }; + const parameters = { bannerId: aliceFile!.id }; const response = await successfulApiCall({ endpoint: 'i/update', parameters: parameters, user: alice }); assert.match(response.bannerUrl ?? '.', /^[-a-zA-Z0-9@:%._\+~#&?=\/]+$/); assert.match(response.bannerBlurhash ?? '.', /[ -~]{54}/); const expected = { ...meDetailed(alice, true), - bannerId: aliceFile.id, + bannerId: aliceFile!.id, bannerBlurhash: response.bannerBlurhash, bannerUrl: response.bannerUrl, }; @@ -579,13 +554,13 @@ describe('ユーザー', () => { //#region ユーザー(users) test.each([ - { label: 'ID昇順', parameters: { limit: 5 }, selector: (u: UserLite): string => u.id }, - { label: 'フォロワー昇順', parameters: { sort: '+follower' }, selector: (u: UserDetailedNotMe): string => String(u.followersCount) }, - { label: 'フォロワー降順', parameters: { sort: '-follower' }, selector: (u: UserDetailedNotMe): string => String(u.followersCount) }, - { label: '登録日時昇順', parameters: { sort: '+createdAt' }, selector: (u: UserDetailedNotMe): string => u.createdAt }, - { label: '登録日時降順', parameters: { sort: '-createdAt' }, selector: (u: UserDetailedNotMe): string => u.createdAt }, - { label: '投稿日時昇順', parameters: { sort: '+updatedAt' }, selector: (u: UserDetailedNotMe): string => String(u.updatedAt) }, - { label: '投稿日時降順', parameters: { sort: '-updatedAt' }, selector: (u: UserDetailedNotMe): string => String(u.updatedAt) }, + { label: 'ID昇順', parameters: { limit: 5 }, selector: (u: misskey.entities.UserLite): string => u.id }, + { label: 'フォロワー昇順', parameters: { sort: '+follower' }, selector: (u: misskey.entities.UserDetailedNotMe): string => String(u.followersCount) }, + { label: 'フォロワー降順', parameters: { sort: '-follower' }, selector: (u: misskey.entities.UserDetailedNotMe): string => String(u.followersCount) }, + { label: '登録日時昇順', parameters: { sort: '+createdAt' }, selector: (u: misskey.entities.UserDetailedNotMe): string => u.createdAt }, + { label: '登録日時降順', parameters: { sort: '-createdAt' }, selector: (u: misskey.entities.UserDetailedNotMe): string => u.createdAt }, + { label: '投稿日時昇順', parameters: { sort: '+updatedAt' }, selector: (u: misskey.entities.UserDetailedNotMe): string => String(u.updatedAt) }, + { label: '投稿日時降順', parameters: { sort: '-updatedAt' }, selector: (u: misskey.entities.UserDetailedNotMe): string => String(u.updatedAt) }, ] as const)('をリスト形式で取得することができる($label)', async ({ parameters, selector }) => { const response = await successfulApiCall({ endpoint: 'users', parameters, user: alice }); @@ -598,15 +573,15 @@ describe('ユーザー', () => { assert.deepStrictEqual(response, expected); }); test.each([ - { label: '「見つけやすくする」がOFFのユーザーが含まれない', user: (): User => userNotExplorable, excluded: true }, - { label: 'ミュートユーザーが含まれない', user: (): User => userMutedByAlice, excluded: true }, - { label: 'ブロックされているユーザーが含まれない', user: (): User => userBlockedByAlice, excluded: true }, - { label: 'ブロックしてきているユーザーが含まれる', user: (): User => userBlockingAlice, excluded: true }, - { label: '承認制ユーザーが含まれる', user: (): User => userLocking }, - { label: 'サイレンスユーザーが含まれる', user: (): User => userSilenced }, - { label: 'サスペンドユーザーが含まれない', user: (): User => userSuspended, excluded: true }, - { label: '削除済ユーザーが含まれる', user: (): User => userDeletedBySelf }, - { label: '削除済(byAdmin)ユーザーが含まれる', user: (): User => userDeletedByAdmin }, + { label: '「見つけやすくする」がOFFのユーザーが含まれない', user: () => userNotExplorable, excluded: true }, + { label: 'ミュートユーザーが含まれない', user: () => userMutedByAlice, excluded: true }, + { label: 'ブロックされているユーザーが含まれない', user: () => userBlockedByAlice, excluded: true }, + { label: 'ブロックしてきているユーザーが含まれる', user: () => userBlockingAlice, excluded: true }, + { label: '承認制ユーザーが含まれる', user: () => userLocking }, + { label: 'サイレンスユーザーが含まれる', user: () => userSilenced }, + { label: 'サスペンドユーザーが含まれない', user: () => userSuspended, excluded: true }, + { label: '削除済ユーザーが含まれる', user: () => userDeletedBySelf }, + { label: '削除済(byAdmin)ユーザーが含まれる', user: () => userDeletedByAdmin }, ] as const)('をリスト形式で取得することができ、結果に$label', async ({ user, excluded }) => { const parameters = { limit: 100 }; const response = await successfulApiCall({ endpoint: 'users', parameters, user: alice }); @@ -620,39 +595,44 @@ describe('ユーザー', () => { //#region ユーザー情報(users/show) test.each([ - { label: 'ID指定で自分自身を', parameters: (): object => ({ userId: alice.id }), user: (): User => alice, type: meDetailed }, - { label: 'ID指定で他人を', parameters: (): object => ({ userId: alice.id }), user: (): User => bob, type: userDetailedNotMeWithRelations }, - { label: 'ID指定かつ未認証', parameters: (): object => ({ userId: alice.id }), user: undefined, type: userDetailedNotMe }, - { label: '@指定で自分自身を', parameters: (): object => ({ username: alice.username }), user: (): User => alice, type: meDetailed }, - { label: '@指定で他人を', parameters: (): object => ({ username: alice.username }), user: (): User => bob, type: userDetailedNotMeWithRelations }, - { label: '@指定かつ未認証', parameters: (): object => ({ username: alice.username }), user: undefined, type: userDetailedNotMe }, + { label: 'ID指定で自分自身を', parameters: () => ({ userId: alice.id }), user: () => alice, type: meDetailed }, + { label: 'ID指定で他人を', parameters: () => ({ userId: alice.id }), user: () => bob, type: userDetailedNotMeWithRelations }, + { label: 'ID指定かつ未認証', parameters: () => ({ userId: alice.id }), user: undefined, type: userDetailedNotMe }, + { label: '@指定で自分自身を', parameters: () => ({ username: alice.username }), user: () => alice, type: meDetailed }, + { label: '@指定で他人を', parameters: () => ({ username: alice.username }), user: () => bob, type: userDetailedNotMeWithRelations }, + { label: '@指定かつ未認証', parameters: () => ({ username: alice.username }), user: undefined, type: userDetailedNotMe }, ] as const)('を取得することができる($label)', async ({ parameters, user, type }) => { const response = await successfulApiCall({ endpoint: 'users/show', parameters: parameters(), user: user?.() }); const expected = type(alice); assert.deepStrictEqual(response, expected); }); test.each([ - { label: 'Administratorになっている', user: (): User => userAdmin, me: (): User => userAdmin, selector: (user: User): unknown => user.isAdmin }, - { label: '自分以外から見たときはAdministratorか判定できない', user: (): User => userAdmin, selector: (user: User): unknown => user.isAdmin, expected: (): undefined => undefined }, - { label: 'Moderatorになっている', user: (): User => userModerator, me: (): User => userModerator, selector: (user: User): unknown => user.isModerator }, - { label: '自分以外から見たときはModeratorか判定できない', user: (): User => userModerator, selector: (user: User): unknown => user.isModerator, expected: (): undefined => undefined }, - { label: 'サイレンスになっている', user: (): User => userSilenced, selector: (user: User): unknown => user.isSilenced }, - //{ label: 'サスペンドになっている', user: (): User => userSuspended, selector: (user: User): unknown => user.isSuspended }, - { label: '削除済みになっている', user: (): User => userDeletedBySelf, me: (): User => userDeletedBySelf, selector: (user: User): unknown => user.isDeleted }, - { label: '自分以外から見たときは削除済みか判定できない', user: (): User => userDeletedBySelf, selector: (user: User): unknown => user.isDeleted, expected: (): undefined => undefined }, - { label: '削除済み(byAdmin)になっている', user: (): User => userDeletedByAdmin, me: (): User => userDeletedByAdmin, selector: (user: User): unknown => user.isDeleted }, - { label: '自分以外から見たときは削除済み(byAdmin)か判定できない', user: (): User => userDeletedByAdmin, selector: (user: User): unknown => user.isDeleted, expected: (): undefined => undefined }, - { label: 'フォロー中になっている', user: (): User => userFollowedByAlice, selector: (user: User): unknown => user.isFollowing }, - { label: 'フォローされている', user: (): User => userFollowingAlice, selector: (user: User): unknown => user.isFollowed }, - { label: 'ブロック中になっている', user: (): User => userBlockedByAlice, selector: (user: User): unknown => user.isBlocking }, - { label: 'ブロックされている', user: (): User => userBlockingAlice, selector: (user: User): unknown => user.isBlocked }, - { label: 'ミュート中になっている', user: (): User => userMutedByAlice, selector: (user: User): unknown => user.isMuted }, - { label: 'リノートミュート中になっている', user: (): User => userRnMutedByAlice, selector: (user: User): unknown => user.isRenoteMuted }, - { label: 'フォローリクエスト中になっている', user: (): User => userFollowRequested, me: (): User => userFollowRequesting, selector: (user: User): unknown => user.hasPendingFollowRequestFromYou }, - { label: 'フォローリクエストされている', user: (): User => userFollowRequesting, me: (): User => userFollowRequested, selector: (user: User): unknown => user.hasPendingFollowRequestToYou }, + { label: 'Administratorになっている', user: () => userAdmin, me: () => userAdmin, selector: (user: misskey.entities.MeDetailed) => user.isAdmin }, + // @ts-expect-error UserDetailedNotMe doesn't include isAdmin + { label: '自分以外から見たときはAdministratorか判定できない', user: () => userAdmin, selector: (user: misskey.entities.UserDetailedNotMe) => user.isAdmin, expected: () => undefined }, + { label: 'Moderatorになっている', user: () => userModerator, me: () => userModerator, selector: (user: misskey.entities.MeDetailed) => user.isModerator }, + // @ts-expect-error UserDetailedNotMe doesn't include isModerator + { label: '自分以外から見たときはModeratorか判定できない', user: () => userModerator, selector: (user: misskey.entities.UserDetailedNotMe) => user.isModerator, expected: () => undefined }, + { label: 'サイレンスになっている', user: () => userSilenced, selector: (user: misskey.entities.UserDetailed) => user.isSilenced }, + // FIXME: 落ちる + //{ label: 'サスペンドになっている', user: () => userSuspended, selector: (user: misskey.entities.UserDetailed) => user.isSuspended }, + { label: '削除済みになっている', user: () => userDeletedBySelf, me: () => userDeletedBySelf, selector: (user: misskey.entities.MeDetailed) => user.isDeleted }, + // @ts-expect-error UserDetailedNotMe doesn't include isDeleted + { label: '自分以外から見たときは削除済みか判定できない', user: () => userDeletedBySelf, selector: (user: misskey.entities.UserDetailedNotMe) => user.isDeleted, expected: () => undefined }, + { label: '削除済み(byAdmin)になっている', user: () => userDeletedByAdmin, me: () => userDeletedByAdmin, selector: (user: misskey.entities.MeDetailed) => user.isDeleted }, + // @ts-expect-error UserDetailedNotMe doesn't include isDeleted + { label: '自分以外から見たときは削除済み(byAdmin)か判定できない', user: () => userDeletedByAdmin, selector: (user: misskey.entities.UserDetailedNotMe) => user.isDeleted, expected: () => undefined }, + { label: 'フォロー中になっている', user: () => userFollowedByAlice, selector: (user: misskey.entities.UserDetailed) => user.isFollowing }, + { label: 'フォローされている', user: () => userFollowingAlice, selector: (user: misskey.entities.UserDetailed) => user.isFollowed }, + { label: 'ブロック中になっている', user: () => userBlockedByAlice, selector: (user: misskey.entities.UserDetailed) => user.isBlocking }, + { label: 'ブロックされている', user: () => userBlockingAlice, selector: (user: misskey.entities.UserDetailed) => user.isBlocked }, + { label: 'ミュート中になっている', user: () => userMutedByAlice, selector: (user: misskey.entities.UserDetailed) => user.isMuted }, + { label: 'リノートミュート中になっている', user: () => userRnMutedByAlice, selector: (user: misskey.entities.UserDetailed) => user.isRenoteMuted }, + { label: 'フォローリクエスト中になっている', user: () => userFollowRequested, me: () => userFollowRequesting, selector: (user: misskey.entities.UserDetailed) => user.hasPendingFollowRequestFromYou }, + { label: 'フォローリクエストされている', user: () => userFollowRequesting, me: () => userFollowRequested, selector: (user: misskey.entities.UserDetailed) => user.hasPendingFollowRequestToYou }, ] as const)('を取得することができ、$labelこと', async ({ user, me, selector, expected }) => { const response = await successfulApiCall({ endpoint: 'users/show', parameters: { userId: user().id }, user: me?.() ?? alice }); - assert.strictEqual(selector(response), (expected ?? ((): true => true))()); + assert.strictEqual(selector(response as any), (expected ?? ((): true => true))()); }); test('を取得することができ、Publicなロールがセットされていること', async () => { const response = await successfulApiCall({ endpoint: 'users/show', parameters: { userId: userRolePublic.id }, user: alice }); @@ -694,17 +674,18 @@ describe('ユーザー', () => { assert.deepStrictEqual(response, expected); }); test.each([ - { label: '「見つけやすくする」がOFFのユーザーが含まれる', user: (): User => userNotExplorable }, - { label: 'ミュートユーザーが含まれる', user: (): User => userMutedByAlice }, - { label: 'ブロックされているユーザーが含まれる', user: (): User => userBlockedByAlice }, - { label: 'ブロックしてきているユーザーが含まれる', user: (): User => userBlockingAlice }, - { label: '承認制ユーザーが含まれる', user: (): User => userLocking }, - { label: 'サイレンスユーザーが含まれる', user: (): User => userSilenced }, - { label: 'サスペンドユーザーが(モデレーターが見るときは)含まれる', user: (): User => userSuspended, me: (): User => root }, + { label: '「見つけやすくする」がOFFのユーザーが含まれる', user: () => userNotExplorable }, + { label: 'ミュートユーザーが含まれる', user: () => userMutedByAlice }, + { label: 'ブロックされているユーザーが含まれる', user: () => userBlockedByAlice }, + { label: 'ブロックしてきているユーザーが含まれる', user: () => userBlockingAlice }, + { label: '承認制ユーザーが含まれる', user: () => userLocking }, + { label: 'サイレンスユーザーが含まれる', user: () => userSilenced }, + { label: 'サスペンドユーザーが(モデレーターが見るときは)含まれる', user: () => userSuspended, me: () => root }, // BUG サスペンドユーザーを一般ユーザーから見るとrootユーザーが返ってくる - //{ label: 'サスペンドユーザーが(一般ユーザーが見るときは)含まれない', user: (): User => userSuspended, me: (): User => bob, excluded: true }, - { label: '削除済ユーザーが含まれる', user: (): User => userDeletedBySelf }, - { label: '削除済(byAdmin)ユーザーが含まれる', user: (): User => userDeletedByAdmin }, + //{ label: 'サスペンドユーザーが(一般ユーザーが見るときは)含まれない', user: () => userSuspended, me: () => bob, excluded: true }, + { label: '削除済ユーザーが含まれる', user: () => userDeletedBySelf }, + { label: '削除済(byAdmin)ユーザーが含まれる', user: () => userDeletedByAdmin }, + // @ts-expect-error excluded は上でコメントアウトされているので ] as const)('をID指定のリスト形式で取得することができ、結果に$label', async ({ user, me, excluded }) => { const parameters = { userIds: [user().id] }; const response = await successfulApiCall({ endpoint: 'users/show', parameters, user: me?.() ?? alice }); @@ -729,15 +710,15 @@ describe('ユーザー', () => { assert.deepStrictEqual(response, expected); }); test.each([ - { label: '「見つけやすくする」がOFFのユーザーが含まれる', user: (): User => userNotExplorable }, - { label: 'ミュートユーザーが含まれる', user: (): User => userMutedByAlice }, - { label: 'ブロックされているユーザーが含まれる', user: (): User => userBlockedByAlice }, - { label: 'ブロックしてきているユーザーが含まれる', user: (): User => userBlockingAlice }, - { label: '承認制ユーザーが含まれる', user: (): User => userLocking }, - { label: 'サイレンスユーザーが含まれる', user: (): User => userSilenced }, - { label: 'サスペンドユーザーが含まれない', user: (): User => userSuspended, excluded: true }, - { label: '削除済ユーザーが含まれる', user: (): User => userDeletedBySelf }, - { label: '削除済(byAdmin)ユーザーが含まれる', user: (): User => userDeletedByAdmin }, + { label: '「見つけやすくする」がOFFのユーザーが含まれる', user: () => userNotExplorable }, + { label: 'ミュートユーザーが含まれる', user: () => userMutedByAlice }, + { label: 'ブロックされているユーザーが含まれる', user: () => userBlockedByAlice }, + { label: 'ブロックしてきているユーザーが含まれる', user: () => userBlockingAlice }, + { label: '承認制ユーザーが含まれる', user: () => userLocking }, + { label: 'サイレンスユーザーが含まれる', user: () => userSilenced }, + { label: 'サスペンドユーザーが含まれない', user: () => userSuspended, excluded: true }, + { label: '削除済ユーザーが含まれる', user: () => userDeletedBySelf }, + { label: '削除済(byAdmin)ユーザーが含まれる', user: () => userDeletedByAdmin }, ] as const)('を検索することができ、結果に$labelが含まれる', async ({ user, excluded }) => { const parameters = { query: user().username, limit: 1 }; const response = await successfulApiCall({ endpoint: 'users/search', parameters, user: alice }); @@ -751,30 +732,30 @@ describe('ユーザー', () => { //#region ID指定検索(users/search-by-username-and-host) test.each([ - { label: '自分', parameters: { username: 'alice' }, user: (): User[] => [alice] }, - { label: '自分かつusernameが大文字', parameters: { username: 'ALICE' }, user: (): User[] => [alice] }, - { label: 'ローカルのフォロイーでノートなし', parameters: { username: 'userFollowedByAlice' }, user: (): User[] => [userFollowedByAlice] }, - { label: 'ローカルでノートなしは検索に載らない', parameters: { username: 'userNoNote' }, user: (): User[] => [] }, - { label: 'ローカルの他人1', parameters: { username: 'bob' }, user: (): User[] => [bob] }, - { label: 'ローカルの他人2', parameters: { username: 'bob', host: null }, user: (): User[] => [bob] }, - { label: 'ローカルの他人3', parameters: { username: 'bob', host: '.' }, user: (): User[] => [bob] }, - { label: 'ローカル', parameters: { host: null, limit: 1 }, user: (): User[] => [userFollowedByAlice] }, - { label: 'ローカル', parameters: { host: '.', limit: 1 }, user: (): User[] => [userFollowedByAlice] }, + { label: '自分', parameters: { username: 'alice' }, user: () => [alice] }, + { label: '自分かつusernameが大文字', parameters: { username: 'ALICE' }, user: () => [alice] }, + { label: 'ローカルのフォロイーでノートなし', parameters: { username: 'userFollowedByAlice' }, user: () => [userFollowedByAlice] }, + { label: 'ローカルでノートなしは検索に載らない', parameters: { username: 'userNoNote' }, user: () => [] }, + { label: 'ローカルの他人1', parameters: { username: 'bob' }, user: () => [bob] }, + { label: 'ローカルの他人2', parameters: { username: 'bob', host: null }, user: () => [bob] }, + { label: 'ローカルの他人3', parameters: { username: 'bob', host: '.' }, user: () => [bob] }, + { label: 'ローカル', parameters: { host: null, limit: 1 }, user: () => [userFollowedByAlice] }, + { label: 'ローカル', parameters: { host: '.', limit: 1 }, user: () => [userFollowedByAlice] }, ])('をID&ホスト指定で検索できる($label)', async ({ parameters, user }) => { const response = await successfulApiCall({ endpoint: 'users/search-by-username-and-host', parameters, user: alice }); const expected = await Promise.all(user().map(u => show(u.id, alice))); assert.deepStrictEqual(response, expected); }); test.each([ - { label: '「見つけやすくする」がOFFのユーザーが含まれる', user: (): User => userNotExplorable }, - { label: 'ミュートユーザーが含まれる', user: (): User => userMutedByAlice }, - { label: 'ブロックされているユーザーが含まれる', user: (): User => userBlockedByAlice }, - { label: 'ブロックしてきているユーザーが含まれる', user: (): User => userBlockingAlice }, - { label: '承認制ユーザーが含まれる', user: (): User => userLocking }, - { label: 'サイレンスユーザーが含まれる', user: (): User => userSilenced }, - { label: 'サスペンドユーザーが含まれない', user: (): User => userSuspended, excluded: true }, - { label: '削除済ユーザーが含まれる', user: (): User => userDeletedBySelf }, - { label: '削除済(byAdmin)ユーザーが含まれる', user: (): User => userDeletedByAdmin }, + { label: '「見つけやすくする」がOFFのユーザーが含まれる', user: () => userNotExplorable }, + { label: 'ミュートユーザーが含まれる', user: () => userMutedByAlice }, + { label: 'ブロックされているユーザーが含まれる', user: () => userBlockedByAlice }, + { label: 'ブロックしてきているユーザーが含まれる', user: () => userBlockingAlice }, + { label: '承認制ユーザーが含まれる', user: () => userLocking }, + { label: 'サイレンスユーザーが含まれる', user: () => userSilenced }, + { label: 'サスペンドユーザーが含まれない', user: () => userSuspended, excluded: true }, + { label: '削除済ユーザーが含まれる', user: () => userDeletedBySelf }, + { label: '削除済(byAdmin)ユーザーが含まれる', user: () => userDeletedByAdmin }, ] as const)('をID&ホスト指定で検索でき、結果に$label', async ({ user, excluded }) => { const parameters = { username: user().username }; const response = await successfulApiCall({ endpoint: 'users/search-by-username-and-host', parameters, user: alice }); @@ -796,15 +777,15 @@ describe('ユーザー', () => { assert.deepStrictEqual(response, expected); }); test.each([ - { label: '「見つけやすくする」がOFFのユーザーが含まれる', user: (): User => userNotExplorable }, - { label: 'ミュートユーザーが含まれる', user: (): User => userMutedByAlice }, - { label: 'ブロックされているユーザーが含まれる', user: (): User => userBlockedByAlice }, - { label: 'ブロックしてきているユーザーが含まれない', user: (): User => userBlockingAlice, excluded: true }, - { label: '承認制ユーザーが含まれる', user: (): User => userLocking }, - { label: 'サイレンスユーザーが含まれる', user: (): User => userSilenced }, - //{ label: 'サスペンドユーザーが含まれない', user: (): User => userSuspended, excluded: true }, - { label: '削除済ユーザーが含まれる', user: (): User => userDeletedBySelf }, - { label: '削除済(byAdmin)ユーザーが含まれる', user: (): User => userDeletedByAdmin }, + { label: '「見つけやすくする」がOFFのユーザーが含まれる', user: () => userNotExplorable }, + { label: 'ミュートユーザーが含まれる', user: () => userMutedByAlice }, + { label: 'ブロックされているユーザーが含まれる', user: () => userBlockedByAlice }, + { label: 'ブロックしてきているユーザーが含まれない', user: () => userBlockingAlice, excluded: true }, + { label: '承認制ユーザーが含まれる', user: () => userLocking }, + { label: 'サイレンスユーザーが含まれる', user: () => userSilenced }, + //{ label: 'サスペンドユーザーが含まれない', user: () => userSuspended, excluded: true }, + { label: '削除済ユーザーが含まれる', user: () => userDeletedBySelf }, + { label: '削除済(byAdmin)ユーザーが含まれる', user: () => userDeletedByAdmin }, ] as const)('がよくリプライをするユーザーのリストを取得でき、結果に$label', async ({ user, excluded }) => { const replyTo = (await successfulApiCall({ endpoint: 'users/notes', parameters: { userId: user().id }, user: undefined }))[0]; await post(alice, { text: `@${user().username} test`, replyId: replyTo.id }); @@ -818,12 +799,12 @@ describe('ユーザー', () => { //#region ハッシュタグ(hashtags/users) test.each([ - { label: 'フォロワー昇順', sort: { sort: '+follower' }, selector: (u: UserDetailedNotMe): string => String(u.followersCount) }, - { label: 'フォロワー降順', sort: { sort: '-follower' }, selector: (u: UserDetailedNotMe): string => String(u.followersCount) }, - { label: '登録日時昇順', sort: { sort: '+createdAt' }, selector: (u: UserDetailedNotMe): string => u.createdAt }, - { label: '登録日時降順', sort: { sort: '-createdAt' }, selector: (u: UserDetailedNotMe): string => u.createdAt }, - { label: '投稿日時昇順', sort: { sort: '+updatedAt' }, selector: (u: UserDetailedNotMe): string => String(u.updatedAt) }, - { label: '投稿日時降順', sort: { sort: '-updatedAt' }, selector: (u: UserDetailedNotMe): string => String(u.updatedAt) }, + { label: 'フォロワー昇順', sort: { sort: '+follower' }, selector: (u: misskey.entities.UserDetailedNotMe): string => String(u.followersCount) }, + { label: 'フォロワー降順', sort: { sort: '-follower' }, selector: (u: misskey.entities.UserDetailedNotMe): string => String(u.followersCount) }, + { label: '登録日時昇順', sort: { sort: '+createdAt' }, selector: (u: misskey.entities.UserDetailedNotMe): string => u.createdAt }, + { label: '登録日時降順', sort: { sort: '-createdAt' }, selector: (u: misskey.entities.UserDetailedNotMe): string => u.createdAt }, + { label: '投稿日時昇順', sort: { sort: '+updatedAt' }, selector: (u: misskey.entities.UserDetailedNotMe): string => String(u.updatedAt) }, + { label: '投稿日時降順', sort: { sort: '-updatedAt' }, selector: (u: misskey.entities.UserDetailedNotMe): string => String(u.updatedAt) }, ] as const)('をハッシュタグ指定で取得することができる($label)', async ({ sort, selector }) => { const hashtag = 'test_hashtag'; await successfulApiCall({ endpoint: 'i/update', parameters: { description: `#${hashtag}` }, user: alice }); @@ -837,15 +818,15 @@ describe('ユーザー', () => { assert.deepStrictEqual(response, expected); }); test.each([ - { label: '「見つけやすくする」がOFFのユーザーが含まれる', user: (): User => userNotExplorable }, - { label: 'ミュートユーザーが含まれる', user: (): User => userMutedByAlice }, - { label: 'ブロックされているユーザーが含まれる', user: (): User => userBlockedByAlice }, - { label: 'ブロックしてきているユーザーが含まれる', user: (): User => userBlockingAlice }, - { label: '承認制ユーザーが含まれる', user: (): User => userLocking }, - { label: 'サイレンスユーザーが含まれる', user: (): User => userSilenced }, - { label: 'サスペンドユーザーが含まれない', user: (): User => userSuspended, excluded: true }, - { label: '削除済ユーザーが含まれる', user: (): User => userDeletedBySelf }, - { label: '削除済(byAdmin)ユーザーが含まれる', user: (): User => userDeletedByAdmin }, + { label: '「見つけやすくする」がOFFのユーザーが含まれる', user: () => userNotExplorable }, + { label: 'ミュートユーザーが含まれる', user: () => userMutedByAlice }, + { label: 'ブロックされているユーザーが含まれる', user: () => userBlockedByAlice }, + { label: 'ブロックしてきているユーザーが含まれる', user: () => userBlockingAlice }, + { label: '承認制ユーザーが含まれる', user: () => userLocking }, + { label: 'サイレンスユーザーが含まれる', user: () => userSilenced }, + { label: 'サスペンドユーザーが含まれない', user: () => userSuspended, excluded: true }, + { label: '削除済ユーザーが含まれる', user: () => userDeletedBySelf }, + { label: '削除済(byAdmin)ユーザーが含まれる', user: () => userDeletedByAdmin }, ] as const)('をハッシュタグ指定で取得することができ、結果に$label', async ({ user, excluded }) => { const hashtag = `user_test${user().username}`; if (user() !== userSuspended) { diff --git a/packages/backend/test/unit/AnnouncementService.ts b/packages/backend/test/unit/AnnouncementService.ts index fc358374204c..aa082ff2f280 100644 --- a/packages/backend/test/unit/AnnouncementService.ts +++ b/packages/backend/test/unit/AnnouncementService.ts @@ -51,7 +51,7 @@ describe('AnnouncementService', () => { function createAnnouncement(data: Partial = {}) { return announcementsRepository.insert({ - id: genAidx(data.createdAt ?? new Date()), + id: genAidx(data.createdAt?.getTime() ?? Date.now()), updatedAt: null, title: 'Title', text: 'Text', diff --git a/packages/backend/test/unit/FetchInstanceMetadataService.ts b/packages/backend/test/unit/FetchInstanceMetadataService.ts index e6e68ccd6db3..510b84b68036 100644 --- a/packages/backend/test/unit/FetchInstanceMetadataService.ts +++ b/packages/backend/test/unit/FetchInstanceMetadataService.ts @@ -19,8 +19,8 @@ import { DI } from '@/di-symbols.js'; import type { TestingModule } from '@nestjs/testing'; function mockRedis() { - const hash = {}; - const set = jest.fn((key, value) => { + const hash = {} as any; + const set = jest.fn((key: string, value) => { const ret = hash[key]; hash[key] = value; return ret; @@ -61,7 +61,7 @@ describe('FetchInstanceMetadataService', () => { app.enableShutdownHooks(); - fetchInstanceMetadataService = app.get(FetchInstanceMetadataService); + fetchInstanceMetadataService = app.get(FetchInstanceMetadataService) as jest.Mocked; federatedInstanceService = app.get(FederatedInstanceService) as jest.Mocked; redisClient = app.get(DI.redis) as jest.Mocked; httpRequestService = app.get(HttpRequestService) as jest.Mocked; @@ -74,11 +74,11 @@ describe('FetchInstanceMetadataService', () => { test('Lock and update', async () => { redisClient.set = mockRedis(); const now = Date.now(); - federatedInstanceService.fetch.mockReturnValue({ infoUpdatedAt: { getTime: () => { return now - 10 * 1000 * 60 * 60 * 24; } } }); + federatedInstanceService.fetch.mockResolvedValue({ infoUpdatedAt: { getTime: () => { return now - 10 * 1000 * 60 * 60 * 24; } } } as any); httpRequestService.getJson.mockImplementation(() => { throw Error(); }); const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock'); const unlockSpy = jest.spyOn(fetchInstanceMetadataService, 'unlock'); - await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' }); + await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any); expect(tryLockSpy).toHaveBeenCalledTimes(1); expect(unlockSpy).toHaveBeenCalledTimes(1); expect(federatedInstanceService.fetch).toHaveBeenCalledTimes(1); @@ -88,11 +88,11 @@ describe('FetchInstanceMetadataService', () => { test('Lock and don\'t update', async () => { redisClient.set = mockRedis(); const now = Date.now(); - federatedInstanceService.fetch.mockReturnValue({ infoUpdatedAt: { getTime: () => now } }); + federatedInstanceService.fetch.mockResolvedValue({ infoUpdatedAt: { getTime: () => now } } as any); httpRequestService.getJson.mockImplementation(() => { throw Error(); }); const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock'); const unlockSpy = jest.spyOn(fetchInstanceMetadataService, 'unlock'); - await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' }); + await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any); expect(tryLockSpy).toHaveBeenCalledTimes(1); expect(unlockSpy).toHaveBeenCalledTimes(1); expect(federatedInstanceService.fetch).toHaveBeenCalledTimes(1); @@ -101,12 +101,13 @@ describe('FetchInstanceMetadataService', () => { test('Do nothing when lock not acquired', async () => { redisClient.set = mockRedis(); - federatedInstanceService.fetch.mockReturnValue({ infoUpdatedAt: { getTime: () => now - 10 * 1000 * 60 * 60 * 24 } }); + const now = Date.now(); + federatedInstanceService.fetch.mockResolvedValue({ infoUpdatedAt: { getTime: () => now - 10 * 1000 * 60 * 60 * 24 } } as any); httpRequestService.getJson.mockImplementation(() => { throw Error(); }); const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock'); const unlockSpy = jest.spyOn(fetchInstanceMetadataService, 'unlock'); await fetchInstanceMetadataService.tryLock('example.com'); - await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' }); + await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any); expect(tryLockSpy).toHaveBeenCalledTimes(2); expect(unlockSpy).toHaveBeenCalledTimes(0); expect(federatedInstanceService.fetch).toHaveBeenCalledTimes(0); diff --git a/packages/backend/test/unit/RoleService.ts b/packages/backend/test/unit/RoleService.ts index fe5ad31597ad..19d03570e00d 100644 --- a/packages/backend/test/unit/RoleService.ts +++ b/packages/backend/test/unit/RoleService.ts @@ -228,11 +228,14 @@ describe('RoleService', () => { }, target: 'conditional', condFormula: { + id: '232a4221-9816-49a6-a967-ae0fac52ec5e', type: 'and', values: [{ + id: '2a37ef43-2d93-4c4d-87f6-f2fdb7d9b530', type: 'followersMoreThanOrEq', value: 10, }, { + id: '1bd67839-b126-4f92-bad0-4e285dab453b', type: 'createdMoreThan', sec: 60 * 60 * 24 * 7, }], diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts index cd5dddd68d8e..86814fffe0e4 100644 --- a/packages/backend/test/utils.ts +++ b/packages/backend/test/utils.ts @@ -9,11 +9,10 @@ import { basename, isAbsolute } from 'node:path'; import { randomUUID } from 'node:crypto'; import { inspect } from 'node:util'; import WebSocket, { ClientOptions } from 'ws'; -import fetch, { File, RequestInit } from 'node-fetch'; +import fetch, { File, RequestInit, type Headers } from 'node-fetch'; import { DataSource } from 'typeorm'; import { JSDOM } from 'jsdom'; import { DEFAULT_POLICIES } from '@/core/RoleService.js'; -import { Packed } from '@/misc/json-schema.js'; import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js'; import { entities } from '../src/postgres.js'; import { loadConfig } from '../src/config.js'; @@ -21,7 +20,7 @@ import type * as misskey from 'misskey-js'; export { server as startServer, jobQueue as startJobQueue } from '@/boot/common.js'; -interface UserToken { +export interface UserToken { token: string; bearer?: boolean; } @@ -35,20 +34,15 @@ export const cookie = (me: UserToken): string => { return `token=${me.token};`; }; -export const api = async (endpoint: string, params: any, me?: UserToken) => { - const normalized = endpoint.replace(/^\//, ''); - return await request(`api/${normalized}`, params, me); -}; - -export type ApiRequest = { - endpoint: string, - parameters: object, +export type ApiRequest = { + endpoint: E, + parameters: P, user: UserToken | undefined, }; -export const successfulApiCall = async (request: ApiRequest, assertion: { +export const successfulApiCall = async (request: ApiRequest, assertion: { status?: number, -} = {}): Promise => { +} = {}): Promise> => { const { endpoint, parameters, user } = request; const res = await api(endpoint, parameters, user); const status = assertion.status ?? (res.body == null ? 204 : 200); @@ -56,7 +50,7 @@ export const successfulApiCall = async (request: ApiRequest, assertion: { return res.body; }; -export const failedApiCall = async (request: ApiRequest, assertion: { +export const failedApiCall = async (request: ApiRequest, assertion: { status: number, code: string, id: string @@ -70,7 +64,7 @@ export const failedApiCall = async (request: ApiRequest, assertion: { return res.body; }; -const request = async (path: string, params: any, me?: UserToken): Promise<{ +export const api = async (path: E, params: misskey.Endpoints[E]['req'], me?: UserToken): Promise<{ status: number, headers: Headers, body: any @@ -86,7 +80,7 @@ const request = async (path: string, params: any, me?: UserToken): Promise<{ bodyAuth.i = me.token; } - const res = await relativeFetch(path, { + const res = await relativeFetch(`api/${path}`, { method: 'POST', headers, body: JSON.stringify(Object.assign(bodyAuth, params)), @@ -141,7 +135,7 @@ export const signup = async (params?: Partial => { +export const post = async (user: UserToken, params: misskey.Endpoints['notes/create']['req']): Promise => { const q = params; const res = await api('notes/create', q, user); @@ -159,8 +153,8 @@ export const createAppToken = async (user: UserToken, permissions: (typeof missk }; // 非公開ノートをAPI越しに見たときのノート NoteEntityService.ts -export const hiddenNote = (note: any): any => { - const temp = { +export const hiddenNote = (note: misskey.entities.Note): misskey.entities.Note => { + const temp: misskey.entities.Note = { ...note, fileIds: [], files: [], @@ -173,21 +167,22 @@ export const hiddenNote = (note: any): any => { return temp; }; -export const react = async (user: UserToken, note: any, reaction: string): Promise => { +export const react = async (user: UserToken, note: misskey.entities.Note, reaction: string): Promise => { await api('notes/reactions/create', { noteId: note.id, reaction: reaction, }, user); }; -export const userList = async (user: UserToken, userList: any = {}): Promise => { +export const userList = async (user: UserToken, userList: Partial = {}): Promise => { const res = await api('users/lists/create', { name: 'test', + ...userList, }, user); return res.body; }; -export const page = async (user: UserToken, page: any = {}): Promise => { +export const page = async (user: UserToken, page: Partial = {}): Promise => { const res = await api('pages/create', { alignCenter: false, content: [ @@ -198,7 +193,7 @@ export const page = async (user: UserToken, page: any = {}): Promise => { }, ], eyeCatchingImageId: null, - font: 'sans-serif', + font: 'sans-serif' as any, hideTitleWhenPinned: false, name: '1678594845072', script: '', @@ -210,7 +205,7 @@ export const page = async (user: UserToken, page: any = {}): Promise => { return res.body; }; -export const play = async (user: UserToken, play: any = {}): Promise => { +export const play = async (user: UserToken, play: Partial = {}): Promise => { const res = await api('flash/create', { permissions: [], script: 'test', @@ -221,7 +216,7 @@ export const play = async (user: UserToken, play: any = {}): Promise => { return res.body; }; -export const clip = async (user: UserToken, clip: any = {}): Promise => { +export const clip = async (user: UserToken, clip: Partial = {}): Promise => { const res = await api('clips/create', { description: null, isPublic: true, @@ -231,18 +226,18 @@ export const clip = async (user: UserToken, clip: any = {}): Promise => { return res.body; }; -export const galleryPost = async (user: UserToken, channel: any = {}): Promise => { +export const galleryPost = async (user: UserToken, galleryPost: Partial = {}): Promise => { const res = await api('gallery/posts/create', { description: null, fileIds: [], isSensitive: false, title: 'test', - ...channel, + ...galleryPost, }, user); return res.body; }; -export const channel = async (user: UserToken, channel: any = {}): Promise => { +export const channel = async (user: UserToken, channel: Partial = {}): Promise => { const res = await api('channels/create', { bannerId: null, description: null, @@ -252,7 +247,7 @@ export const channel = async (user: UserToken, channel: any = {}): Promise return res.body; }; -export const role = async (user: UserToken, role: any = {}, policies: any = {}): Promise => { +export const role = async (user: UserToken, role: Partial = {}, policies: any = {}): Promise => { const res = await api('admin/roles/create', { asBadge: false, canEditMembersByModerator: false, @@ -260,7 +255,7 @@ export const role = async (user: UserToken, role: any = {}, policies: any = {}): condFormula: { id: 'ebef1684-672d-49b6-ad82-1b3ec3784f85', type: 'isRemote', - }, + } as any, description: '', displayOrder: 0, iconUrl: null, @@ -298,7 +293,7 @@ interface UploadOptions { export const uploadFile = async (user?: UserToken, { path, name, blob }: UploadOptions = {}): Promise<{ status: number, headers: Headers, - body: misskey.Endpoints['drive/files/create']['res'] | null + body: misskey.entities.DriveFile | null }> => { const absPath = path == null ? new URL('resources/Lenna.jpg', import.meta.url) @@ -335,14 +330,14 @@ export const uploadFile = async (user?: UserToken, { path, name, blob }: UploadO }; }; -export const uploadUrl = async (user: UserToken, url: string): Promise> => { +export const uploadUrl = async (user: UserToken, url: string): Promise => { const marker = Math.random().toString(); const catcher = makeStreamCatcher( user, 'main', (msg) => msg.type === 'urlUploadFinished' && msg.body.marker === marker, - (msg) => msg.body.file as Packed<'DriveFile'>, + (msg) => msg.body.file, 60 * 1000, ); From 983480131bf6ac48b3834d334deb97a6b3f2f4f6 Mon Sep 17 00:00:00 2001 From: tamaina Date: Mon, 4 Mar 2024 12:54:13 +0900 Subject: [PATCH 003/192] chore: Automated release (#13075) * chore: Automated release * follow --- .github/workflows/release-edit-with-push.yml | 40 ++++++ .github/workflows/release-with-dispatch.yml | 122 +++++++++++++++++++ .github/workflows/release-with-ready.yml | 38 ++++++ 3 files changed, 200 insertions(+) create mode 100644 .github/workflows/release-edit-with-push.yml create mode 100644 .github/workflows/release-with-dispatch.yml create mode 100644 .github/workflows/release-with-ready.yml diff --git a/.github/workflows/release-edit-with-push.yml b/.github/workflows/release-edit-with-push.yml new file mode 100644 index 000000000000..944b98eb7cc2 --- /dev/null +++ b/.github/workflows/release-edit-with-push.yml @@ -0,0 +1,40 @@ +name: "Release Manager: sync changelog with PR" + +on: + push: + branches: + - release/** + paths: + - 'CHANGELOG.md' + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +permissions: + contents: write + issues: write + pull-requests: write + +jobs: + edit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + # headがrelease/かつopenのPRを1つ取得 + - name: Get PR + run: | + echo "pr_number=$(gh pr list --limit 1 --head "${{ github.ref_name }}" --json number --jq '.[] | .number')" >> $GITHUB_OUTPUT + id: get_pr + - name: Get target version + uses: misskey-dev/release-manager-actions/.github/actions/get-target-version@v1 + id: v + # CHANGELOG.mdの内容を取得 + - name: Get changelog + uses: misskey-dev/release-manager-actions/.github/actions/get-changelog@v1 + with: + version: ${{ steps.v.outputs.target_version }} + id: changelog + # PRのnotesを更新 + - name: Update PR + run: | + gh pr edit ${{ steps.get_pr.outputs.pr_number }} --body "${{ steps.changelog.outputs.changelog }}" diff --git a/.github/workflows/release-with-dispatch.yml b/.github/workflows/release-with-dispatch.yml new file mode 100644 index 000000000000..1a954739d959 --- /dev/null +++ b/.github/workflows/release-with-dispatch.yml @@ -0,0 +1,122 @@ +name: "Release Manager [Dispatch]" + +on: + workflow_dispatch: + inputs: + ## Specify the type of the next release. + #version_increment_type: + # type: choice + # description: 'VERSION INCREMENT TYPE' + # default: 'patch' + # required: false + # options: + # - 'major' + # - 'minor' + # - 'patch' + merge: + type: boolean + description: 'MERGE RELEASE BRANCH TO MAIN' + default: false + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +permissions: + contents: write + issues: write + pull-requests: write + +jobs: + get-pr: + runs-on: ubuntu-latest + outputs: + pr_number: ${{ steps.get_pr.outputs.pr_number }} + steps: + - uses: actions/checkout@v4 + # headがrelease/かつopenのPRを1つ取得 + - name: Get PRs + run: | + echo "pr_number=$(gh pr list --limit 1 --search "head:release/ is:open" --json number --jq '.[] | .number')" >> $GITHUB_OUTPUT + id: get_pr + + merge: + uses: misskey-dev/release-manager-actions/.github/workflows/merge.yml@v1 + needs: get-pr + if: ${{ needs.get-pr.outputs.pr_number != '' && inputs.merge == true }} + with: + pr_number: ${{ needs.get-pr.outputs.pr_number }} + package_jsons_to_rewrite: ${{ vars.PACKAGE_JSONS_TO_REWRITE }} + # Text to prepend to the changelog + # The first line must be `## Unreleased` + changes_template: | + ## Unreleased + + ### General + - + + ### Client + - + + ### Server + - + + use_external_app_to_release: ${{ vars.USE_RELEASE_APP == 'true' }} + secrets: + RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }} + RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + RULESET_EDIT_APP_ID: ${{ secrets.RULESET_EDIT_APP_ID }} + RULESET_EDIT_APP_PRIVATE_KEY: ${{ secrets.RULESET_EDIT_APP_PRIVATE_KEY }} + + create-prerelease: + uses: misskey-dev/release-manager-actions/.github/workflows/create-prerelease.yml@v1 + needs: get-pr + if: ${{ needs.get-pr.outputs.pr_number != '' && inputs.merge != true }} + with: + pr_number: ${{ needs.get-pr.outputs.pr_number }} + package_jsons_to_rewrite: ${{ vars.PACKAGE_JSONS_TO_REWRITE }} + use_external_app_to_release: ${{ vars.USE_RELEASE_APP == 'true' }} + secrets: + RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }} + RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + + create-target: + uses: misskey-dev/release-manager-actions/.github/workflows/create-target.yml@v1 + needs: get-pr + if: ${{ needs.get-pr.outputs.pr_number == '' }} + with: + # The script for version increment. + # process.env.CURRENT_VERSION: The current version. + # + # Misskey calender versioning (yyyy.MM.patch) example + version_increment_script: | + const now = new Date(); + const year = now.toLocaleDateString('en-US', { year: 'numeric', timeZone: 'Asia/Tokyo' }); + const month = now.toLocaleDateString('en-US', { month: 'numeric', timeZone: 'Asia/Tokyo' }); + const [major, minor, _patch] = process.env.CURRENT_VERSION.split('.'); + const patch = Number(_patch.split('-')[0]); + if (Number.isNaN(patch)) { + console.error('Invalid patch version', year, month, process.env.CURRENT_VERSION, major, minor, _patch); + throw new Error('Invalid patch version'); + } + if (year !== major || month !== minor) { + return `${year}.${month}.0`; + } else { + return `${major}.${minor}.${patch + 1}`; + } + ##Semver example + #version_increment_script: | + # const [major, minor, patch] = process.env.CURRENT_VERSION.split('.'); + # if ("${{ inputs.version_increment_type }}" === "major") { + # return `${Number(major) + 1}.0.0`; + # } else if ("${{ inputs.version_increment_type }}" === "minor") { + # return `${major}.${Number(minor) + 1}.0`; + # } else { + # return `${major}.${minor}.${Number(patch) + 1}`; + # } + package_jsons_to_rewrite: ${{ vars.PACKAGE_JSONS_TO_REWRITE }} + use_external_app_to_release: ${{ vars.USE_RELEASE_APP == 'true' }} + secrets: + RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }} + RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + RULESET_EDIT_APP_ID: ${{ secrets.RULESET_EDIT_APP_ID }} + RULESET_EDIT_APP_PRIVATE_KEY: ${{ secrets.RULESET_EDIT_APP_PRIVATE_KEY }} diff --git a/.github/workflows/release-with-ready.yml b/.github/workflows/release-with-ready.yml new file mode 100644 index 000000000000..b64ed20791b0 --- /dev/null +++ b/.github/workflows/release-with-ready.yml @@ -0,0 +1,38 @@ +name: "Release Manager: release RC when ready for review" + +on: + pull_request: + types: [ready_for_review] + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +permissions: + contents: write + issues: write + pull-requests: write + +jobs: + check: + runs-on: ubuntu-latest + outputs: + ref: ${{ steps.get_pr.outputs.ref }} + steps: + - uses: actions/checkout@v4 + # PR情報を取得 + - name: Get PR + run: | + pr_json=$(gh pr view ${{ github.event.pull_request.number }} --json isDraft,headRefName) + echo "ref=$(echo $pr_json | jq -r '.headRefName')" >> $GITHUB_OUTPUT + id: get_pr + release: + uses: misskey-dev/release-manager-actions/.github/workflows/create-prerelease.yml@v1 + needs: check + if: startsWith(needs.check.outputs.ref, 'release/') + with: + pr_number: ${{ github.event.pull_request.number }} + package_jsons_to_rewrite: ${{ vars.PACKAGE_JSONS_TO_REWRITE }} + use_external_app_to_release: ${{ vars.USE_RELEASE_APP == 'true' }} + secrets: + RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }} + RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} From 9542cb8d6253a93b06a68b9ac3647367f8f7354c Mon Sep 17 00:00:00 2001 From: tamaina Date: Mon, 4 Mar 2024 13:48:57 +0900 Subject: [PATCH 004/192] =?UTF-8?q?fix(backend):=20=E3=83=AA=E3=83=A2?= =?UTF-8?q?=E3=83=BC=E3=83=88=E3=82=B5=E3=83=BC=E3=83=90=E3=83=BC=E3=81=AE?= =?UTF-8?q?=E6=83=85=E5=A0=B1=E3=81=8C=E6=9B=B4=E6=96=B0=E3=81=A7=E3=81=8D?= =?UTF-8?q?=E3=81=AA=E3=81=8F=E3=81=AA=E3=81=A3=E3=81=A6=E3=81=84=E3=81=9F?= =?UTF-8?q?=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3=20(#13507)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(backend): fetchInstanceMetadataのLockが永遠に解除されない問題を修正 Co-authored-by: まっちゃとーにゅ <17376330+u1-liquid@users.noreply.github.com> * fix test * fix * comment * comment * improve test --------- Co-authored-by: まっちゃとーにゅ <17376330+u1-liquid@users.noreply.github.com> --- .../src/core/FetchInstanceMetadataService.ts | 28 ++++++++++++++----- .../test/unit/FetchInstanceMetadataService.ts | 24 ++++++++++++++-- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/packages/backend/src/core/FetchInstanceMetadataService.ts b/packages/backend/src/core/FetchInstanceMetadataService.ts index bc270bd28fd3..8d173855f33d 100644 --- a/packages/backend/src/core/FetchInstanceMetadataService.ts +++ b/packages/backend/src/core/FetchInstanceMetadataService.ts @@ -51,21 +51,35 @@ export class FetchInstanceMetadataService { } @bindThis - public async tryLock(host: string): Promise { - const mutex = await this.redisClient.set(`fetchInstanceMetadata:mutex:${host}`, '1', 'GET'); - return mutex !== '1'; + // public for test + public async tryLock(host: string): Promise { + // TODO: マイグレーションなのであとで消す (2024.3.1) + this.redisClient.del(`fetchInstanceMetadata:mutex:${host}`); + + return await this.redisClient.set( + `fetchInstanceMetadata:mutex:v2:${host}`, '1', + 'EX', 30, // 30秒したら自動でロック解除 https://github.com/misskey-dev/misskey/issues/13506#issuecomment-1975375395 + 'GET' // 古い値を返す(なかったらnull) + ); } @bindThis - public unlock(host: string): Promise<'OK'> { - return this.redisClient.set(`fetchInstanceMetadata:mutex:${host}`, '0'); + // public for test + public unlock(host: string): Promise { + return this.redisClient.del(`fetchInstanceMetadata:mutex:v2:${host}`); } @bindThis public async fetchInstanceMetadata(instance: MiInstance, force = false): Promise { const host = instance.host; - // Acquire mutex to ensure no parallel runs - if (!await this.tryLock(host)) return; + + // finallyでunlockされてしまうのでtry内でロックチェックをしない + // (returnであってもfinallyは実行される) + if (!force && await this.tryLock(host) === '1') { + // 1が返ってきていたらロックされているという意味なので、何もしない + return; + } + try { if (!force) { const _instance = await this.federatedInstanceService.fetch(host); diff --git a/packages/backend/test/unit/FetchInstanceMetadataService.ts b/packages/backend/test/unit/FetchInstanceMetadataService.ts index 510b84b68036..bf8f3ab0e306 100644 --- a/packages/backend/test/unit/FetchInstanceMetadataService.ts +++ b/packages/backend/test/unit/FetchInstanceMetadataService.ts @@ -56,6 +56,7 @@ describe('FetchInstanceMetadataService', () => { } else if (token === DI.redis) { return mockRedis; } + return null; }) .compile(); @@ -78,6 +79,7 @@ describe('FetchInstanceMetadataService', () => { httpRequestService.getJson.mockImplementation(() => { throw Error(); }); const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock'); const unlockSpy = jest.spyOn(fetchInstanceMetadataService, 'unlock'); + await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any); expect(tryLockSpy).toHaveBeenCalledTimes(1); expect(unlockSpy).toHaveBeenCalledTimes(1); @@ -92,6 +94,7 @@ describe('FetchInstanceMetadataService', () => { httpRequestService.getJson.mockImplementation(() => { throw Error(); }); const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock'); const unlockSpy = jest.spyOn(fetchInstanceMetadataService, 'unlock'); + await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any); expect(tryLockSpy).toHaveBeenCalledTimes(1); expect(unlockSpy).toHaveBeenCalledTimes(1); @@ -104,13 +107,30 @@ describe('FetchInstanceMetadataService', () => { const now = Date.now(); federatedInstanceService.fetch.mockResolvedValue({ infoUpdatedAt: { getTime: () => now - 10 * 1000 * 60 * 60 * 24 } } as any); httpRequestService.getJson.mockImplementation(() => { throw Error(); }); + await fetchInstanceMetadataService.tryLock('example.com'); const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock'); const unlockSpy = jest.spyOn(fetchInstanceMetadataService, 'unlock'); - await fetchInstanceMetadataService.tryLock('example.com'); + await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any); - expect(tryLockSpy).toHaveBeenCalledTimes(2); + expect(tryLockSpy).toHaveBeenCalledTimes(1); expect(unlockSpy).toHaveBeenCalledTimes(0); expect(federatedInstanceService.fetch).toHaveBeenCalledTimes(0); expect(httpRequestService.getJson).toHaveBeenCalledTimes(0); }); + + test('Do when lock not acquired but forced', async () => { + redisClient.set = mockRedis(); + const now = Date.now(); + federatedInstanceService.fetch.mockResolvedValue({ infoUpdatedAt: { getTime: () => now - 10 * 1000 * 60 * 60 * 24 } } as any); + httpRequestService.getJson.mockImplementation(() => { throw Error(); }); + await fetchInstanceMetadataService.tryLock('example.com'); + const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock'); + const unlockSpy = jest.spyOn(fetchInstanceMetadataService, 'unlock'); + + await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any, true); + expect(tryLockSpy).toHaveBeenCalledTimes(0); + expect(unlockSpy).toHaveBeenCalledTimes(1); + expect(federatedInstanceService.fetch).toHaveBeenCalledTimes(0); + expect(httpRequestService.getJson).toHaveBeenCalled(); + }); }); From 96ab1af03b821ac265a1e8ff492c154b80e02759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Mon, 4 Mar 2024 16:09:24 +0900 Subject: [PATCH 005/192] Update CHANGELOG.md --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bafee277d259..ca7bf85fd061 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,17 @@ --> +## Unreleased + +### General +- + +### Client +- + +### Server +- + ## 2024.3.1 ### General From 13f5fafdbc869207141f2a2f1f75f61c3147372d Mon Sep 17 00:00:00 2001 From: tamaina Date: Mon, 4 Mar 2024 10:39:43 +0000 Subject: [PATCH 006/192] remove template txt --- CHANGELOG.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca7bf85fd061..349e99d13399 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,3 @@ - - ## Unreleased ### General From 83a5bc0ecd05a352e164bda1f66f48962159c427 Mon Sep 17 00:00:00 2001 From: tamaina Date: Tue, 5 Mar 2024 14:26:16 +0900 Subject: [PATCH 007/192] =?UTF-8?q?=20doc:=20Nest=E3=81=A7=E5=BE=AA?= =?UTF-8?q?=E7=92=B0=E4=BE=9D=E5=AD=98=E3=81=8C=E3=81=82=E3=82=8B=E5=A0=B4?= =?UTF-8?q?=E5=90=88=E3=81=AECONTRIBUTING.md=E3=81=AB=E6=9B=B8=E3=81=8F=20?= =?UTF-8?q?=20(#13522)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * doc: Nestモジュールテストの例をCONTRIBUTING.mdに書く * rm normal test * forwardRef --- CONTRIBUTING.md | 92 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a3263bf6aa13..dcb625626d6f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -316,6 +316,98 @@ export const handlers = [ Don't forget to re-run the `.storybook/generate.js` script after adding, editing, or removing the above files. +## Nest + +### Nest Service Circular dependency / Nestでサービスの循環参照でエラーが起きた場合 + +#### forwardRef +まずは簡単に`forwardRef`を試してみる + +```typescript +export class FooService { + constructor( + @Inject(forwardRef(() => BarService)) + private barService: BarService + ) { + } +} +``` + +#### OnModuleInit +できなければ`OnModuleInit`を使う + +```typescript +import { Injectable, OnModuleInit } from '@nestjs/common'; +import { ModuleRef } from '@nestjs/core'; +import { BarService } from '@/core/BarService'; + +@Injectable() +export class FooService implements OnModuleInit { + private barService: BarService // constructorから移動してくる + + constructor( + private moduleRef: ModuleRef, + ) { + } + + async onModuleInit() { + this.barService = this.moduleRef.get(BarService.name); + } + + public async niceMethod() { + return await this.barService.incredibleMethod({ hoge: 'fuga' }); + } +} +``` + +##### Service Unit Test +テストで`onModuleInit`を呼び出す必要がある + +```typescript +// import ... + +describe('test', () => { + let app: TestingModule; + let fooService: FooService; // for test case + let barService: BarService; // for test case + + beforeEach(async () => { + app = await Test.createTestingModule({ + imports: ..., + providers: [ + FooService, + { // mockする (mockは必須ではないかもしれない) + provide: BarService, + useFactory: () => ({ + incredibleMethod: jest.fn(), + }), + }, + { // Provideにする + provide: BarService.name, + useExisting: BarService, + }, + ], + }) + .useMocker(... + .compile(); + + fooService = app.get(FooService); + barService = app.get(BarService) as jest.Mocked; + + // onModuleInitを実行する + await fooService.onModuleInit(); + }); + + test('nice', () => { + await fooService.niceMethod(); + + expect(barService.incredibleMethod).toHaveBeenCalled(); + expect(barService.incredibleMethod.mock.lastCall![0]) + .toEqual({ hoge: 'fuga' }); + }); +}) +``` + ## Notes ### Misskeyのドメイン固有の概念は`Mi`をprefixする From 45672a70f9f8fd932896468f9bfdc6c511013682 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Tue, 5 Mar 2024 17:27:33 +0900 Subject: [PATCH 008/192] =?UTF-8?q?fix(frontend):=20router=E9=81=B7?= =?UTF-8?q?=E7=A7=BB=E6=99=82=E3=81=ABmatchAll=E3=81=AB=E5=85=A5=E3=81=A3?= =?UTF-8?q?=E3=81=9F=E5=A0=B4=E5=90=88=E4=B8=80=E5=BA=A6`location.href`?= =?UTF-8?q?=E3=82=92=E7=B5=8C=E7=94=B1=E3=81=99=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=20(#13509)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(frontend): router遷移時にmatchAllに入った場合一度`location.href`を経由するように * Update Changelog * Update CHANGELOG.md * remove unnecessary args --- CHANGELOG.md | 2 +- packages/frontend/src/nirax.ts | 20 ++++++++++++-------- packages/frontend/vite.config.local-dev.ts | 3 +++ 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 349e99d13399..0cebaabffdc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ - ### Client -- +- Fix: 一部のページ内リンクが正しく動作しない問題を修正 ### Server - diff --git a/packages/frontend/src/nirax.ts b/packages/frontend/src/nirax.ts index 616fb104e6c0..6a8ea09ed6af 100644 --- a/packages/frontend/src/nirax.ts +++ b/packages/frontend/src/nirax.ts @@ -373,7 +373,7 @@ export class Router extends EventEmitter implements IRouter { this.currentRoute.value = res.route; this.currentKey = res.route.globalCacheKey ?? key ?? path; - if (emitChange) { + if (emitChange && res.route.path !== '/:(*)') { this.emit('change', { beforePath, path, @@ -408,13 +408,17 @@ export class Router extends EventEmitter implements IRouter { if (cancel) return; } const res = this.navigate(path, null); - this.emit('push', { - beforePath, - path: res._parsedRoute.fullPath, - route: res.route, - props: res.props, - key: this.currentKey, - }); + if (res.route.path === '/:(*)') { + location.href = path; + } else { + this.emit('push', { + beforePath, + path: res._parsedRoute.fullPath, + route: res.route, + props: res.props, + key: this.currentKey, + }); + } } public replace(path: string, key?: string | null) { diff --git a/packages/frontend/vite.config.local-dev.ts b/packages/frontend/vite.config.local-dev.ts index 6d9488797c72..460787fd0589 100644 --- a/packages/frontend/vite.config.local-dev.ts +++ b/packages/frontend/vite.config.local-dev.ts @@ -48,6 +48,9 @@ const devConfig = { }, '/url': httpUrl, '/proxy': httpUrl, + '/_info_card_': httpUrl, + '/bios': httpUrl, + '/cli': httpUrl, }, }, build: { From 08d618bb8b98199a4670313019d6a85ad4cf155b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Tue, 5 Mar 2024 18:06:57 +0900 Subject: [PATCH 009/192] =?UTF-8?q?enhance(frontend):=20=E8=87=AA=E5=88=86?= =?UTF-8?q?=E3=81=AE=E3=83=8E=E3=83=BC=E3=83=88=E3=81=AE=E6=B7=BB=E4=BB=98?= =?UTF-8?q?=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E3=81=8B=E3=82=89=E7=9B=B4?= =?UTF-8?q?=E6=8E=A5=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E3=81=AE=E8=A9=B3?= =?UTF-8?q?=E7=B4=B0=E3=83=9A=E3=83=BC=E3=82=B8=E3=81=AB=E9=A3=9B=E3=81=B9?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=81=99=E3=82=8B=20(#1352?= =?UTF-8?q?0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * enhance(frontend): 自分のノートの添付ファイルから直接ファイルの詳細ページに飛べるようにする * 他のファイルタイプにも対応 * Update Changelog --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com> --- CHANGELOG.md | 1 + .../src/core/entities/DriveFileEntityService.ts | 2 +- packages/frontend/src/components/MkMediaAudio.vue | 15 ++++++++++++--- packages/frontend/src/components/MkMediaImage.vue | 9 ++++++++- packages/frontend/src/components/MkMediaVideo.vue | 15 ++++++++++++--- 5 files changed, 34 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cebaabffdc6..1b9009427929 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - ### Client +- Enhance: 自分のノートの添付ファイルから直接ファイルの詳細ページに飛べるように - Fix: 一部のページ内リンクが正しく動作しない問題を修正 ### Server diff --git a/packages/backend/src/core/entities/DriveFileEntityService.ts b/packages/backend/src/core/entities/DriveFileEntityService.ts index 8affe2b3bf84..26bf386cbc4b 100644 --- a/packages/backend/src/core/entities/DriveFileEntityService.ts +++ b/packages/backend/src/core/entities/DriveFileEntityService.ts @@ -248,7 +248,7 @@ export class DriveFileEntityService { folder: opts.detail && file.folderId ? this.driveFolderEntityService.pack(file.folderId, { detail: true, }) : null, - userId: opts.withUser ? file.userId : null, + userId: file.userId, user: (opts.withUser && file.userId) ? this.userEntityService.pack(file.userId) : null, }); } diff --git a/packages/frontend/src/components/MkMediaAudio.vue b/packages/frontend/src/components/MkMediaAudio.vue index d42146f941f4..96c9b9fd66c8 100644 --- a/packages/frontend/src/components/MkMediaAudio.vue +++ b/packages/frontend/src/components/MkMediaAudio.vue @@ -66,7 +66,7 @@ import * as os from '@/os.js'; import bytes from '@/filters/bytes.js'; import { hms } from '@/filters/hms.js'; import MkMediaRange from '@/components/MkMediaRange.vue'; -import { iAmModerator } from '@/account.js'; +import { $i, iAmModerator } from '@/account.js'; const props = defineProps<{ audio: Misskey.entities.DriveFile; @@ -96,8 +96,6 @@ function showMenu(ev: MouseEvent) { if (iAmModerator) { menu.push({ - type: 'divider', - }, { text: props.audio.isSensitive ? i18n.ts.unmarkAsSensitive : i18n.ts.markAsSensitive, icon: props.audio.isSensitive ? 'ti ti-eye' : 'ti ti-eye-exclamation', danger: true, @@ -105,6 +103,17 @@ function showMenu(ev: MouseEvent) { }); } + if ($i?.id === props.audio.userId) { + menu.push({ + type: 'divider', + }, { + type: 'link' as const, + text: i18n.ts._fileViewer.title, + icon: 'ti ti-info-circle', + to: `/my/drive/file/${props.audio.id}`, + }); + } + menuShowing.value = true; os.popupMenu(menu, ev.currentTarget ?? ev.target, { align: 'right', diff --git a/packages/frontend/src/components/MkMediaImage.vue b/packages/frontend/src/components/MkMediaImage.vue index 4ba2c7613315..82f36fe5c412 100644 --- a/packages/frontend/src/components/MkMediaImage.vue +++ b/packages/frontend/src/components/MkMediaImage.vue @@ -59,7 +59,7 @@ import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue'; import { defaultStore } from '@/store.js'; import { i18n } from '@/i18n.js'; import * as os from '@/os.js'; -import { iAmModerator } from '@/account.js'; +import { $i, iAmModerator } from '@/account.js'; const props = withDefaults(defineProps<{ image: Misskey.entities.DriveFile; @@ -114,6 +114,13 @@ function showMenu(ev: MouseEvent) { action: () => { os.apiWithDialog('drive/files/update', { fileId: props.image.id, isSensitive: true }); }, + }] : []), ...($i?.id === props.image.userId ? [{ + type: 'divider' as const, + }, { + type: 'link' as const, + text: i18n.ts._fileViewer.title, + icon: 'ti ti-info-circle', + to: `/my/drive/file/${props.image.id}`, }] : [])], ev.currentTarget ?? ev.target); } diff --git a/packages/frontend/src/components/MkMediaVideo.vue b/packages/frontend/src/components/MkMediaVideo.vue index eab4fdfd6bd3..73c1b6ff9d55 100644 --- a/packages/frontend/src/components/MkMediaVideo.vue +++ b/packages/frontend/src/components/MkMediaVideo.vue @@ -94,7 +94,7 @@ import * as os from '@/os.js'; import { isFullscreenNotSupported } from '@/scripts/device-kind.js'; import hasAudio from '@/scripts/media-has-audio.js'; import MkMediaRange from '@/components/MkMediaRange.vue'; -import { iAmModerator } from '@/account.js'; +import { $i, iAmModerator } from '@/account.js'; const props = defineProps<{ video: Misskey.entities.DriveFile; @@ -122,8 +122,6 @@ function showMenu(ev: MouseEvent) { if (iAmModerator) { menu.push({ - type: 'divider', - }, { text: props.video.isSensitive ? i18n.ts.unmarkAsSensitive : i18n.ts.markAsSensitive, icon: props.video.isSensitive ? 'ti ti-eye' : 'ti ti-eye-exclamation', danger: true, @@ -131,6 +129,17 @@ function showMenu(ev: MouseEvent) { }); } + if ($i?.id === props.video.userId) { + menu.push({ + type: 'divider', + }, { + type: 'link' as const, + text: i18n.ts._fileViewer.title, + icon: 'ti ti-info-circle', + to: `/my/drive/file/${props.video.id}`, + }); + } + menuShowing.value = true; os.popupMenu(menu, ev.currentTarget ?? ev.target, { align: 'right', From 4457b02db2ca7591afa8683141e4b223170ea367 Mon Sep 17 00:00:00 2001 From: tamaina Date: Wed, 6 Mar 2024 08:08:32 +0000 Subject: [PATCH 010/192] =?UTF-8?q?fix(frontend)=3F:=20importAppScript?= =?UTF-8?q?=E3=81=AFimport=E3=82=92await=E3=81=99=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/server/web/boot.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js index 59441826b07d..396536948ef4 100644 --- a/packages/backend/src/server/web/boot.js +++ b/packages/backend/src/server/web/boot.js @@ -86,8 +86,8 @@ //#endregion //#region Script - function importAppScript() { - import(`/vite/${CLIENT_ENTRY}`) + async function importAppScript() { + await import(`/vite/${CLIENT_ENTRY}`) .catch(async e => { console.error(e); renderError('APP_IMPORT', e); From 00c1e4eb550c68f43ae44ba9f0c8da9887fc2180 Mon Sep 17 00:00:00 2001 From: tamaina Date: Wed, 6 Mar 2024 09:40:47 +0000 Subject: [PATCH 011/192] =?UTF-8?q?perf:=20boot.js=E3=81=AE=E8=AA=BF?= =?UTF-8?q?=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/server/web/boot.js | 501 ++++++++++++++---------- 1 file changed, 288 insertions(+), 213 deletions(-) diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js index 396536948ef4..bc7b800d2299 100644 --- a/packages/backend/src/server/web/boot.js +++ b/packages/backend/src/server/web/boot.js @@ -7,158 +7,163 @@ * BOOT LOADER * サーバーからレスポンスされるHTMLに埋め込まれるスクリプトで、以下の役割を持ちます。 * - 翻訳ファイルをフェッチする。 - * - バージョンに基づいて適切なメインスクリプトを読み込む。 + * - 事前に挿入されたCLIENT_ENTRYを読んで適切なメインスクリプトを読み込む。 * - キャッシュされたコンパイル済みテーマを適用する。 * - クライアントの設定値に基づいて対応するHTMLクラス等を設定する。 + * - もしメインスクリプトの読み込みなどでエラーが発生した場合は、renderErrorでエラーを描画する。 * テーマをこの段階で設定するのは、メインスクリプトが読み込まれる間もテーマを適用したいためです。 - * 注: webpackは介さないため、このファイルではrequireやimportは使えません。 */ 'use strict'; +var misskey_loader = new Set(); + // ブロックの中に入れないと、定義した変数がブラウザのグローバルスコープに登録されてしまい邪魔なので -(async () => { - window.onerror = (e) => { - console.error(e); - renderError('SOMETHING_HAPPENED', e); - }; - window.onunhandledrejection = (e) => { - console.error(e); - renderError('SOMETHING_HAPPENED_IN_PROMISE', e); - }; +function boot() { + const defaultSolutions = [ + 'Clear the browser cache / ブラウザのキャッシュをクリアする', + 'Update your os and browser / ブラウザおよびOSを最新バージョンに更新する', + 'Disable an adblocker / アドブロッカーを無効にする', + '(Tor Browser) Set dom.webaudio.enabled to true / dom.webaudio.enabledをtrueに設定する' + ]; + + const onErrorStyle = ` + * { + font-family: BIZ UDGothic, Roboto, HelveticaNeue, Arial, sans-serif; + } - let forceError = localStorage.getItem('forceError'); - if (forceError != null) { - renderError('FORCED_ERROR', 'This error is forced by having forceError in local storage.') + #misskey_app, + #splash { + display: none !important; } - //#region Detect language & fetch translations - if (!localStorage.hasOwnProperty('locale')) { - const supportedLangs = LANGS; - let lang = localStorage.getItem('lang'); - if (lang == null || !supportedLangs.includes(lang)) { - if (supportedLangs.includes(navigator.language)) { - lang = navigator.language; - } else { - lang = supportedLangs.find(x => x.split('-')[0] === navigator.language); + body, + html { + background-color: #222; + color: #dfddcc; + justify-content: center; + margin: auto; + padding: 10px; + text-align: center; + } - // Fallback - if (lang == null) lang = 'en-US'; - } - } + button { + border-radius: 999px; + padding: 0px 12px 0px 12px; + border: none; + cursor: pointer; + margin-bottom: 12px; + } - const metaRes = await window.fetch('/api/meta', { - method: 'POST', - body: JSON.stringify({}), - credentials: 'omit', - cache: 'no-cache', - headers: { - 'Content-Type': 'application/json', - }, - }); - if (metaRes.status !== 200) { - renderError('META_FETCH'); - return; - } - const meta = await metaRes.json(); - const v = meta.version; - if (v == null) { - renderError('META_FETCH_V'); - return; - } + .button-big { + background: linear-gradient(90deg, rgb(134, 179, 0), rgb(74, 179, 0)); + line-height: 50px; + } - // for https://github.com/misskey-dev/misskey/issues/10202 - if (lang == null || lang.toString == null || lang.toString() === 'null') { - console.error('invalid lang value detected!!!', typeof lang, lang); - lang = 'en-US'; - } + .button-big:hover { + background: rgb(153, 204, 0); + } - const localRes = await window.fetch(`/assets/locales/${lang}.${v}.json`); - if (localRes.status === 200) { - localStorage.setItem('lang', lang); - localStorage.setItem('locale', await localRes.text()); - localStorage.setItem('localeVersion', v); - } else { - renderError('LOCALE_FETCH'); - return; - } + .button-small { + background: #444; + line-height: 40px; } - //#endregion - //#region Script - async function importAppScript() { - await import(`/vite/${CLIENT_ENTRY}`) - .catch(async e => { - console.error(e); - renderError('APP_IMPORT', e); - }); + .button-small:hover { + background: #555; } - // タイミングによっては、この時点でDOMの構築が済んでいる場合とそうでない場合とがある - if (document.readyState !== 'loading') { - importAppScript(); - } else { - window.addEventListener('DOMContentLoaded', () => { - importAppScript(); - }); + .button-label-big { + color: #222; + font-weight: bold; + font-size: 1.2em; + padding: 12px; } - //#endregion - //#region Theme - const theme = localStorage.getItem('theme'); - if (theme) { - for (const [k, v] of Object.entries(JSON.parse(theme))) { - document.documentElement.style.setProperty(`--${k}`, v.toString()); + .button-label-small { + color: rgb(153, 204, 0); + font-size: 16px; + padding: 12px; + } - // HTMLの theme-color 適用 - if (k === 'htmlThemeColor') { - for (const tag of document.head.children) { - if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') { - tag.setAttribute('content', v); - break; - } - } - } - } + a { + color: rgb(134, 179, 0); + text-decoration: none; } - const colorScheme = localStorage.getItem('colorScheme'); - if (colorScheme) { - document.documentElement.style.setProperty('color-scheme', colorScheme); + + p, + li { + font-size: 16px; } - //#endregion - const fontSize = localStorage.getItem('fontSize'); - if (fontSize) { - document.documentElement.classList.add('f-' + fontSize); + .icon-warning { + color: #dec340; + height: 4rem; + padding-top: 2rem; } - const useSystemFont = localStorage.getItem('useSystemFont'); - if (useSystemFont) { - document.documentElement.classList.add('useSystemFont'); + h1 { + font-size: 1.5em; + margin: 1em; } - const wallpaper = localStorage.getItem('wallpaper'); - if (wallpaper) { - document.documentElement.style.backgroundImage = `url(${wallpaper})`; + summary { + cursor: pointer; } - const customCss = localStorage.getItem('customCss'); - if (customCss && customCss.length > 0) { - const style = document.createElement('style'); - style.innerHTML = customCss; - document.head.appendChild(style); + code { + font-family: Fira, FiraCode, monospace; + } + + #errors { + display: flex; + flex-direction: column; + align-items: center; } - async function addStyle(styleText) { - let css = document.createElement('style'); - css.appendChild(document.createTextNode(styleText)); - document.head.appendChild(css); + .errorInfo { + background: #333; + width: 40rem; + max-width: 100%; + border-radius: 10px; + justify-content: center; + padding: 1rem; + margin-bottom: 1rem; + box-sizing: border-box; } - function renderError(code, details) { + .errorInfo > pre { + text-wrap: auto; + text-wrap: balance; + } + `; + + + const addStyle = (styleText) => { + try { + let css = document.createElement('style'); + css.appendChild(document.createTextNode(styleText)); + document.head.appendChild(css); + } catch (e) { + console.error(e); + } + } + + const renderError = (code, details, solutions = defaultSolutions) => { + if (document.readyState === 'loading') { + window.addEventListener('DOMContentLoaded', () => { + renderError(code, details, solutions); + }); + try { + addStyle(onErrorStyle); + } catch (e) { } + return; + } + let errorsElement = document.getElementById('errors'); if (!errorsElement) { + // エラー描画用のビューになっていない場合は、エラー描画用のビューに切り替える document.body.innerHTML = ` @@ -170,10 +175,7 @@ Reload / リロード

The following actions may solve the problem. / 以下を行うと解決する可能性があります。

-

Clear the browser cache / ブラウザのキャッシュをクリアする

-

Update your os and browser / ブラウザおよびOSを最新バージョンに更新する

-

Disable an adblocker / アドブロッカーを無効にする

-

(Tor Browser) Set dom.webaudio.enabled to true / dom.webaudio.enabledをtrueに設定する

+ ${solutions.map(x => `

${x}

`).join('')}
Other options / その他のオプション @@ -199,121 +201,194 @@ `; errorsElement = document.getElementById('errors'); } - const detailsElement = document.createElement('details'); - detailsElement.id = 'errorInfo'; - detailsElement.innerHTML = ` -
- - ERROR CODE: ${code} - - ${JSON.stringify(details)}`; - errorsElement.appendChild(detailsElement); - addStyle(` - * { - font-family: BIZ UDGothic, Roboto, HelveticaNeue, Arial, sans-serif; - } - #misskey_app, - #splash { - display: none !important; - } + if (typeof details === 'string') { + const errorEl = document.createElement('div'); + errorEl.classList.add('errorInfo'); + + const titleCodeElement = document.createElement('code'); + titleCodeElement.textContent = `ERROR CODE: ${code}`; + errorEl.appendChild(titleCodeElement); + + errorEl.appendChild(document.createElement('br')); + + const detailsCodeElement = document.createElement('code'); + detailsCodeElement.textContent = details; + errorEl.appendChild(detailsCodeElement); + + errorsElement.appendChild(errorEl); + } else if (details instanceof Error) { + const errorEl = document.createElement('details'); + errorEl.classList.add('errorInfo'); + + const summaryElement = document.createElement('summary'); + const titleCodeElement = document.createElement('code'); + titleCodeElement.textContent = `ERROR CODE: ${code}`; + summaryElement.appendChild(titleCodeElement); + errorEl.appendChild(summaryElement); + + const detailsPreElement = document.createElement('pre'); + const detailsMessageElement = document.createElement('code'); + detailsMessageElement.textContent = details.message; + detailsPreElement.appendChild(detailsMessageElement); + detailsPreElement.appendChild(document.createElement('br')); + const detailsCodeElement = document.createElement('code'); + detailsCodeElement.textContent = details.stack; + detailsPreElement.appendChild(detailsCodeElement); + errorEl.appendChild(detailsPreElement); + + errorsElement.appendChild(errorEl); + } else { + const errorEl = document.createElement('details'); + errorEl.classList.add('errorInfo'); - body, - html { - background-color: #222; - color: #dfddcc; - justify-content: center; - margin: auto; - padding: 10px; - text-align: center; - } + const summaryElement = document.createElement('summary'); + const titleCodeElement = document.createElement('code'); + titleCodeElement.textContent = `ERROR CODE: ${code}`; + summaryElement.appendChild(titleCodeElement); + errorEl.appendChild(summaryElement); - button { - border-radius: 999px; - padding: 0px 12px 0px 12px; - border: none; - cursor: pointer; - margin-bottom: 12px; - } + const detailsCodeElement = document.createElement('code'); + detailsCodeElement.textContent = JSON.stringify(details); + errorEl.appendChild(detailsCodeElement); - .button-big { - background: linear-gradient(90deg, rgb(134, 179, 0), rgb(74, 179, 0)); - line-height: 50px; + errorsElement.appendChild(errorEl); } - .button-big:hover { - background: rgb(153, 204, 0); - } + addStyle(onErrorStyle); + } - .button-small { - background: #444; - line-height: 40px; - } + window.onerror = (e) => { + console.error(e); + renderError('SOMETHING_HAPPENED', e); + }; + window.onunhandledrejection = (e) => { + console.error(e); + renderError('SOMETHING_HAPPENED_IN_PROMISE', e); + }; - .button-small:hover { - background: #555; - } + let forceError = localStorage.getItem('forceError'); + if (forceError != null) { + renderError('FORCED_ERROR', 'This error is forced by having forceError in local storage.') + renderError('FORCED_ERROR', Error('This error is forced by having forceError in local storage.')); + return; + } - .button-label-big { - color: #222; - font-weight: bold; - font-size: 1.2em; - padding: 12px; + //#region After DOM loaded + async function oncontentload() { + const providedMetaEl = document.getElementById('misskey_meta'); + const meta = providedMetaEl && providedMetaEl.textContent ? JSON.parse(providedMetaEl.textContent) : null; + const providedAt = providedMetaEl && providedMetaEl.dataset.generatedAt ? parseInt(providedMetaEl.dataset.generatedAt) : 0; + console.log('providedAt', providedAt, 'now', Date.now()); + if (providedAt < Date.now() - 1000 * 60 * 60 * 24) { + // 古いデータがなぜか提供された場合は、エラーを描画する + renderError( + 'META_PROVIDED_AT_TOO_OLD', + 'This view is too old. Please reload.', + [ + 'Reload / リロードする', + 'Clear the browser cache then reload / ブラウザのキャッシュをクリアしてリロードする', + 'Disable an adblocker / アドブロッカーを無効にする', + ] + ); + return; } - .button-label-small { - color: rgb(153, 204, 0); - font-size: 16px; - padding: 12px; - } + //#region Detect language & fetch translations on first load + if (!localStorage.hasOwnProperty('locale')) { + const supportedLangs = LANGS; + let lang = localStorage.getItem('lang'); + if (lang == null || !supportedLangs.includes(lang)) { + if (supportedLangs.includes(navigator.language)) { + lang = navigator.language; + } else { + lang = supportedLangs.find(x => x.split('-')[0] === navigator.language); + + // Fallback + if (lang == null) lang = 'en-US'; + } + } - a { - color: rgb(134, 179, 0); - text-decoration: none; - } + const v = meta?.version; - p, - li { - font-size: 16px; - } + // for https://github.com/misskey-dev/misskey/issues/10202 + if (lang == null || lang.toString == null || lang.toString() === 'null') { + console.warn('invalid lang value detected!!!', typeof lang, lang); + lang = 'en-US'; + } - .icon-warning { - color: #dec340; - height: 4rem; - padding-top: 2rem; + const localRes = await window.fetch(`/assets/locales/${lang}.${v}.json`); + if (localRes.status === 200) { + localStorage.setItem('lang', lang); + localStorage.setItem('locale', await localRes.text()); + localStorage.setItem('localeVersion', v); + } else { + renderError('LOCALE_FETCH'); + return; + } } + //#endregion - h1 { - font-size: 1.5em; - margin: 1em; - } + await import(`/vite/${CLIENT_ENTRY}`) + .catch(async e => { + console.error(e); + renderError('APP_IMPORT', e); + }); + } - code { - font-family: Fira, FiraCode, monospace; - } + if (document.readyState !== 'loading') { + misskey_loader.add(oncontentload()); + } else { + window.addEventListener('DOMContentLoaded', () => { + misskey_loader.add(oncontentload()); + }); + } + //#endregion - #errorInfo { - background: #333; - margin-bottom: 2rem; - padding: 0.5rem 1rem; - width: 40rem; - border-radius: 10px; - justify-content: center; - margin: auto; - } + //#region Theme + const theme = localStorage.getItem('theme'); + if (theme) { + for (const [k, v] of Object.entries(JSON.parse(theme))) { + document.documentElement.style.setProperty(`--${k}`, v.toString()); - #errorInfo summary { - cursor: pointer; + // HTMLの theme-color 適用 + if (k === 'htmlThemeColor') { + for (const tag of document.head.children) { + if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') { + tag.setAttribute('content', v); + break; + } + } + } } + } + const colorScheme = localStorage.getItem('colorScheme'); + if (colorScheme) { + document.documentElement.style.setProperty('color-scheme', colorScheme); + } + //#endregion - #errorInfo summary > * { - display: inline; - } + const fontSize = localStorage.getItem('fontSize'); + if (fontSize) { + document.documentElement.classList.add('f-' + fontSize); + } - @media screen and (max-width: 500px) { - #errorInfo { - width: 50%; - } - `) + const useSystemFont = localStorage.getItem('useSystemFont'); + if (useSystemFont) { + document.documentElement.classList.add('useSystemFont'); } -})(); + + const wallpaper = localStorage.getItem('wallpaper'); + if (wallpaper) { + document.documentElement.style.backgroundImage = `url(${wallpaper})`; + } + + const customCss = localStorage.getItem('customCss'); + if (customCss && customCss.length > 0) { + const style = document.createElement('style'); + style.innerHTML = customCss; + document.head.appendChild(style); + } +} + +boot(); From 62922352b3cddaee9f72261448d862002e55a67c Mon Sep 17 00:00:00 2001 From: tamaina Date: Wed, 6 Mar 2024 09:49:01 +0000 Subject: [PATCH 012/192] =?UTF-8?q?Revert=20"perf:=20boot.js=E3=81=AE?= =?UTF-8?q?=E8=AA=BF=E6=95=B4"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 00c1e4eb550c68f43ae44ba9f0c8da9887fc2180. --- packages/backend/src/server/web/boot.js | 501 ++++++++++-------------- 1 file changed, 213 insertions(+), 288 deletions(-) diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js index bc7b800d2299..396536948ef4 100644 --- a/packages/backend/src/server/web/boot.js +++ b/packages/backend/src/server/web/boot.js @@ -7,163 +7,158 @@ * BOOT LOADER * サーバーからレスポンスされるHTMLに埋め込まれるスクリプトで、以下の役割を持ちます。 * - 翻訳ファイルをフェッチする。 - * - 事前に挿入されたCLIENT_ENTRYを読んで適切なメインスクリプトを読み込む。 + * - バージョンに基づいて適切なメインスクリプトを読み込む。 * - キャッシュされたコンパイル済みテーマを適用する。 * - クライアントの設定値に基づいて対応するHTMLクラス等を設定する。 - * - もしメインスクリプトの読み込みなどでエラーが発生した場合は、renderErrorでエラーを描画する。 * テーマをこの段階で設定するのは、メインスクリプトが読み込まれる間もテーマを適用したいためです。 + * 注: webpackは介さないため、このファイルではrequireやimportは使えません。 */ 'use strict'; -var misskey_loader = new Set(); - // ブロックの中に入れないと、定義した変数がブラウザのグローバルスコープに登録されてしまい邪魔なので -function boot() { - const defaultSolutions = [ - 'Clear the browser cache / ブラウザのキャッシュをクリアする', - 'Update your os and browser / ブラウザおよびOSを最新バージョンに更新する', - 'Disable an adblocker / アドブロッカーを無効にする', - '(Tor Browser) Set dom.webaudio.enabled to true / dom.webaudio.enabledをtrueに設定する' - ]; - - const onErrorStyle = ` - * { - font-family: BIZ UDGothic, Roboto, HelveticaNeue, Arial, sans-serif; - } - - #misskey_app, - #splash { - display: none !important; - } - - body, - html { - background-color: #222; - color: #dfddcc; - justify-content: center; - margin: auto; - padding: 10px; - text-align: center; - } - - button { - border-radius: 999px; - padding: 0px 12px 0px 12px; - border: none; - cursor: pointer; - margin-bottom: 12px; - } +(async () => { + window.onerror = (e) => { + console.error(e); + renderError('SOMETHING_HAPPENED', e); + }; + window.onunhandledrejection = (e) => { + console.error(e); + renderError('SOMETHING_HAPPENED_IN_PROMISE', e); + }; - .button-big { - background: linear-gradient(90deg, rgb(134, 179, 0), rgb(74, 179, 0)); - line-height: 50px; + let forceError = localStorage.getItem('forceError'); + if (forceError != null) { + renderError('FORCED_ERROR', 'This error is forced by having forceError in local storage.') } - .button-big:hover { - background: rgb(153, 204, 0); - } + //#region Detect language & fetch translations + if (!localStorage.hasOwnProperty('locale')) { + const supportedLangs = LANGS; + let lang = localStorage.getItem('lang'); + if (lang == null || !supportedLangs.includes(lang)) { + if (supportedLangs.includes(navigator.language)) { + lang = navigator.language; + } else { + lang = supportedLangs.find(x => x.split('-')[0] === navigator.language); - .button-small { - background: #444; - line-height: 40px; - } + // Fallback + if (lang == null) lang = 'en-US'; + } + } - .button-small:hover { - background: #555; - } + const metaRes = await window.fetch('/api/meta', { + method: 'POST', + body: JSON.stringify({}), + credentials: 'omit', + cache: 'no-cache', + headers: { + 'Content-Type': 'application/json', + }, + }); + if (metaRes.status !== 200) { + renderError('META_FETCH'); + return; + } + const meta = await metaRes.json(); + const v = meta.version; + if (v == null) { + renderError('META_FETCH_V'); + return; + } - .button-label-big { - color: #222; - font-weight: bold; - font-size: 1.2em; - padding: 12px; - } + // for https://github.com/misskey-dev/misskey/issues/10202 + if (lang == null || lang.toString == null || lang.toString() === 'null') { + console.error('invalid lang value detected!!!', typeof lang, lang); + lang = 'en-US'; + } - .button-label-small { - color: rgb(153, 204, 0); - font-size: 16px; - padding: 12px; + const localRes = await window.fetch(`/assets/locales/${lang}.${v}.json`); + if (localRes.status === 200) { + localStorage.setItem('lang', lang); + localStorage.setItem('locale', await localRes.text()); + localStorage.setItem('localeVersion', v); + } else { + renderError('LOCALE_FETCH'); + return; + } } + //#endregion - a { - color: rgb(134, 179, 0); - text-decoration: none; + //#region Script + async function importAppScript() { + await import(`/vite/${CLIENT_ENTRY}`) + .catch(async e => { + console.error(e); + renderError('APP_IMPORT', e); + }); } - p, - li { - font-size: 16px; + // タイミングによっては、この時点でDOMの構築が済んでいる場合とそうでない場合とがある + if (document.readyState !== 'loading') { + importAppScript(); + } else { + window.addEventListener('DOMContentLoaded', () => { + importAppScript(); + }); } + //#endregion - .icon-warning { - color: #dec340; - height: 4rem; - padding-top: 2rem; - } + //#region Theme + const theme = localStorage.getItem('theme'); + if (theme) { + for (const [k, v] of Object.entries(JSON.parse(theme))) { + document.documentElement.style.setProperty(`--${k}`, v.toString()); - h1 { - font-size: 1.5em; - margin: 1em; + // HTMLの theme-color 適用 + if (k === 'htmlThemeColor') { + for (const tag of document.head.children) { + if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') { + tag.setAttribute('content', v); + break; + } + } + } + } } - - summary { - cursor: pointer; + const colorScheme = localStorage.getItem('colorScheme'); + if (colorScheme) { + document.documentElement.style.setProperty('color-scheme', colorScheme); } + //#endregion - code { - font-family: Fira, FiraCode, monospace; + const fontSize = localStorage.getItem('fontSize'); + if (fontSize) { + document.documentElement.classList.add('f-' + fontSize); } - #errors { - display: flex; - flex-direction: column; - align-items: center; + const useSystemFont = localStorage.getItem('useSystemFont'); + if (useSystemFont) { + document.documentElement.classList.add('useSystemFont'); } - .errorInfo { - background: #333; - width: 40rem; - max-width: 100%; - border-radius: 10px; - justify-content: center; - padding: 1rem; - margin-bottom: 1rem; - box-sizing: border-box; + const wallpaper = localStorage.getItem('wallpaper'); + if (wallpaper) { + document.documentElement.style.backgroundImage = `url(${wallpaper})`; } - .errorInfo > pre { - text-wrap: auto; - text-wrap: balance; + const customCss = localStorage.getItem('customCss'); + if (customCss && customCss.length > 0) { + const style = document.createElement('style'); + style.innerHTML = customCss; + document.head.appendChild(style); } - `; - - const addStyle = (styleText) => { - try { - let css = document.createElement('style'); - css.appendChild(document.createTextNode(styleText)); - document.head.appendChild(css); - } catch (e) { - console.error(e); - } + async function addStyle(styleText) { + let css = document.createElement('style'); + css.appendChild(document.createTextNode(styleText)); + document.head.appendChild(css); } - const renderError = (code, details, solutions = defaultSolutions) => { - if (document.readyState === 'loading') { - window.addEventListener('DOMContentLoaded', () => { - renderError(code, details, solutions); - }); - try { - addStyle(onErrorStyle); - } catch (e) { } - return; - } - + function renderError(code, details) { let errorsElement = document.getElementById('errors'); if (!errorsElement) { - // エラー描画用のビューになっていない場合は、エラー描画用のビューに切り替える document.body.innerHTML = ` @@ -175,7 +170,10 @@ function boot() { Reload / リロード

The following actions may solve the problem. / 以下を行うと解決する可能性があります。

- ${solutions.map(x => `

${x}

`).join('')} +

Clear the browser cache / ブラウザのキャッシュをクリアする

+

Update your os and browser / ブラウザおよびOSを最新バージョンに更新する

+

Disable an adblocker / アドブロッカーを無効にする

+

(Tor Browser) Set dom.webaudio.enabled to true / dom.webaudio.enabledをtrueに設定する

Other options / その他のオプション @@ -201,194 +199,121 @@ function boot() { `; errorsElement = document.getElementById('errors'); } + const detailsElement = document.createElement('details'); + detailsElement.id = 'errorInfo'; + detailsElement.innerHTML = ` +
+ + ERROR CODE: ${code} + + ${JSON.stringify(details)}`; + errorsElement.appendChild(detailsElement); + addStyle(` + * { + font-family: BIZ UDGothic, Roboto, HelveticaNeue, Arial, sans-serif; + } - if (typeof details === 'string') { - const errorEl = document.createElement('div'); - errorEl.classList.add('errorInfo'); - - const titleCodeElement = document.createElement('code'); - titleCodeElement.textContent = `ERROR CODE: ${code}`; - errorEl.appendChild(titleCodeElement); - - errorEl.appendChild(document.createElement('br')); - - const detailsCodeElement = document.createElement('code'); - detailsCodeElement.textContent = details; - errorEl.appendChild(detailsCodeElement); - - errorsElement.appendChild(errorEl); - } else if (details instanceof Error) { - const errorEl = document.createElement('details'); - errorEl.classList.add('errorInfo'); - - const summaryElement = document.createElement('summary'); - const titleCodeElement = document.createElement('code'); - titleCodeElement.textContent = `ERROR CODE: ${code}`; - summaryElement.appendChild(titleCodeElement); - errorEl.appendChild(summaryElement); - - const detailsPreElement = document.createElement('pre'); - const detailsMessageElement = document.createElement('code'); - detailsMessageElement.textContent = details.message; - detailsPreElement.appendChild(detailsMessageElement); - detailsPreElement.appendChild(document.createElement('br')); - const detailsCodeElement = document.createElement('code'); - detailsCodeElement.textContent = details.stack; - detailsPreElement.appendChild(detailsCodeElement); - errorEl.appendChild(detailsPreElement); - - errorsElement.appendChild(errorEl); - } else { - const errorEl = document.createElement('details'); - errorEl.classList.add('errorInfo'); - - const summaryElement = document.createElement('summary'); - const titleCodeElement = document.createElement('code'); - titleCodeElement.textContent = `ERROR CODE: ${code}`; - summaryElement.appendChild(titleCodeElement); - errorEl.appendChild(summaryElement); - - const detailsCodeElement = document.createElement('code'); - detailsCodeElement.textContent = JSON.stringify(details); - errorEl.appendChild(detailsCodeElement); + #misskey_app, + #splash { + display: none !important; + } - errorsElement.appendChild(errorEl); + body, + html { + background-color: #222; + color: #dfddcc; + justify-content: center; + margin: auto; + padding: 10px; + text-align: center; } - addStyle(onErrorStyle); - } + button { + border-radius: 999px; + padding: 0px 12px 0px 12px; + border: none; + cursor: pointer; + margin-bottom: 12px; + } - window.onerror = (e) => { - console.error(e); - renderError('SOMETHING_HAPPENED', e); - }; - window.onunhandledrejection = (e) => { - console.error(e); - renderError('SOMETHING_HAPPENED_IN_PROMISE', e); - }; + .button-big { + background: linear-gradient(90deg, rgb(134, 179, 0), rgb(74, 179, 0)); + line-height: 50px; + } - let forceError = localStorage.getItem('forceError'); - if (forceError != null) { - renderError('FORCED_ERROR', 'This error is forced by having forceError in local storage.') - renderError('FORCED_ERROR', Error('This error is forced by having forceError in local storage.')); - return; - } + .button-big:hover { + background: rgb(153, 204, 0); + } - //#region After DOM loaded - async function oncontentload() { - const providedMetaEl = document.getElementById('misskey_meta'); - const meta = providedMetaEl && providedMetaEl.textContent ? JSON.parse(providedMetaEl.textContent) : null; - const providedAt = providedMetaEl && providedMetaEl.dataset.generatedAt ? parseInt(providedMetaEl.dataset.generatedAt) : 0; - console.log('providedAt', providedAt, 'now', Date.now()); - if (providedAt < Date.now() - 1000 * 60 * 60 * 24) { - // 古いデータがなぜか提供された場合は、エラーを描画する - renderError( - 'META_PROVIDED_AT_TOO_OLD', - 'This view is too old. Please reload.', - [ - 'Reload / リロードする', - 'Clear the browser cache then reload / ブラウザのキャッシュをクリアしてリロードする', - 'Disable an adblocker / アドブロッカーを無効にする', - ] - ); - return; + .button-small { + background: #444; + line-height: 40px; } - //#region Detect language & fetch translations on first load - if (!localStorage.hasOwnProperty('locale')) { - const supportedLangs = LANGS; - let lang = localStorage.getItem('lang'); - if (lang == null || !supportedLangs.includes(lang)) { - if (supportedLangs.includes(navigator.language)) { - lang = navigator.language; - } else { - lang = supportedLangs.find(x => x.split('-')[0] === navigator.language); - - // Fallback - if (lang == null) lang = 'en-US'; - } - } + .button-small:hover { + background: #555; + } - const v = meta?.version; + .button-label-big { + color: #222; + font-weight: bold; + font-size: 1.2em; + padding: 12px; + } - // for https://github.com/misskey-dev/misskey/issues/10202 - if (lang == null || lang.toString == null || lang.toString() === 'null') { - console.warn('invalid lang value detected!!!', typeof lang, lang); - lang = 'en-US'; - } + .button-label-small { + color: rgb(153, 204, 0); + font-size: 16px; + padding: 12px; + } - const localRes = await window.fetch(`/assets/locales/${lang}.${v}.json`); - if (localRes.status === 200) { - localStorage.setItem('lang', lang); - localStorage.setItem('locale', await localRes.text()); - localStorage.setItem('localeVersion', v); - } else { - renderError('LOCALE_FETCH'); - return; - } + a { + color: rgb(134, 179, 0); + text-decoration: none; } - //#endregion - await import(`/vite/${CLIENT_ENTRY}`) - .catch(async e => { - console.error(e); - renderError('APP_IMPORT', e); - }); - } + p, + li { + font-size: 16px; + } - if (document.readyState !== 'loading') { - misskey_loader.add(oncontentload()); - } else { - window.addEventListener('DOMContentLoaded', () => { - misskey_loader.add(oncontentload()); - }); - } - //#endregion + .icon-warning { + color: #dec340; + height: 4rem; + padding-top: 2rem; + } - //#region Theme - const theme = localStorage.getItem('theme'); - if (theme) { - for (const [k, v] of Object.entries(JSON.parse(theme))) { - document.documentElement.style.setProperty(`--${k}`, v.toString()); + h1 { + font-size: 1.5em; + margin: 1em; + } - // HTMLの theme-color 適用 - if (k === 'htmlThemeColor') { - for (const tag of document.head.children) { - if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') { - tag.setAttribute('content', v); - break; - } - } - } + code { + font-family: Fira, FiraCode, monospace; } - } - const colorScheme = localStorage.getItem('colorScheme'); - if (colorScheme) { - document.documentElement.style.setProperty('color-scheme', colorScheme); - } - //#endregion - const fontSize = localStorage.getItem('fontSize'); - if (fontSize) { - document.documentElement.classList.add('f-' + fontSize); - } + #errorInfo { + background: #333; + margin-bottom: 2rem; + padding: 0.5rem 1rem; + width: 40rem; + border-radius: 10px; + justify-content: center; + margin: auto; + } - const useSystemFont = localStorage.getItem('useSystemFont'); - if (useSystemFont) { - document.documentElement.classList.add('useSystemFont'); - } + #errorInfo summary { + cursor: pointer; + } - const wallpaper = localStorage.getItem('wallpaper'); - if (wallpaper) { - document.documentElement.style.backgroundImage = `url(${wallpaper})`; - } + #errorInfo summary > * { + display: inline; + } - const customCss = localStorage.getItem('customCss'); - if (customCss && customCss.length > 0) { - const style = document.createElement('style'); - style.innerHTML = customCss; - document.head.appendChild(style); + @media screen and (max-width: 500px) { + #errorInfo { + width: 50%; + } + `) } -} - -boot(); +})(); From 7ead98cbe592e6911e4a54550cb7bb507e782d7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Wed, 6 Mar 2024 21:08:42 +0900 Subject: [PATCH 013/192] =?UTF-8?q?enhance(frontend):=20=E3=83=AA=E3=82=A2?= =?UTF-8?q?=E3=82=AF=E3=82=B7=E3=83=A7=E3=83=B3=E3=81=AE=E7=B7=8F=E6=95=B0?= =?UTF-8?q?=E3=82=92=E8=A1=A8=E7=A4=BA=E3=81=99=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=20(#13532)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * enhance(frontend): リアクションの総数を表示するように * Update Changelog * リアクション選択済の色をaccentに --- CHANGELOG.md | 2 ++ locales/index.d.ts | 4 +++ locales/ja-JP.yml | 1 + .../src/core/entities/NoteEntityService.ts | 1 + .../backend/src/models/json-schema/note.ts | 4 +++ packages/frontend/src/components/MkNote.vue | 25 ++++++++++----- .../src/components/MkNoteDetailed.vue | 31 ++++++++++++------- .../src/components/MkNotification.vue | 27 ++++++++++------ .../src/components/MkTutorialDialog.Note.vue | 1 + .../components/MkTutorialDialog.PostNote.vue | 1 + .../components/MkTutorialDialog.Sensitive.vue | 1 + .../frontend/src/scripts/use-note-capture.ts | 2 ++ packages/frontend/src/style.scss | 7 +++++ packages/misskey-js/src/autogen/types.ts | 1 + 14 files changed, 79 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b9009427929..7bdfa53e9965 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ ### Client - Enhance: 自分のノートの添付ファイルから直接ファイルの詳細ページに飛べるように +- Enhance: リアクション・いいねの総数を表示するように +- Enhance: リアクション受け入れが「いいねのみ」の場合はリアクション絵文字一覧を表示しないように - Fix: 一部のページ内リンクが正しく動作しない問題を修正 ### Server diff --git a/locales/index.d.ts b/locales/index.d.ts index c1aa163f9822..3ac4ff9e74cd 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -8909,6 +8909,10 @@ export interface Locale extends ILocale { * {n}人がリアクションしました */ "reactedBySomeUsers": ParameterizedString<"n">; + /** + * {n}人がいいねしました + */ + "likedBySomeUsers": ParameterizedString<"n">; /** * {n}人がリノートしました */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 51380e49c523..177d6a06c941 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -2355,6 +2355,7 @@ _notification: sendTestNotification: "テスト通知を送信する" notificationWillBeDisplayedLikeThis: "通知はこのように表示されます" reactedBySomeUsers: "{n}人がリアクションしました" + likedBySomeUsers: "{n}人がいいねしました" renotedBySomeUsers: "{n}人がリノートしました" followedBySomeUsers: "{n}人にフォローされました" flushNotification: "通知の履歴をリセットする" diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index 5b6affc6a520..22d01462e773 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -333,6 +333,7 @@ export class NoteEntityService implements OnModuleInit { visibleUserIds: note.visibility === 'specified' ? note.visibleUserIds : undefined, renoteCount: note.renoteCount, repliesCount: note.repliesCount, + reactionCount: Object.values(note.reactions).reduce((a, b) => a + b, 0), reactions: this.reactionService.convertLegacyReactions(note.reactions), reactionEmojis: this.customEmojiService.populateEmojis(reactionEmojiNames, host), reactionAndUserPairCache: opts.withReactionAndUserPairCache ? note.reactionAndUserPairCache : undefined, diff --git a/packages/backend/src/models/json-schema/note.ts b/packages/backend/src/models/json-schema/note.ts index bb4ccc7ee4db..2641161c8ba2 100644 --- a/packages/backend/src/models/json-schema/note.ts +++ b/packages/backend/src/models/json-schema/note.ts @@ -223,6 +223,10 @@ export const packedNoteSchema = { }], }, }, + reactionCount: { + type: 'number', + optional: false, nullable: false, + }, renoteCount: { type: 'number', optional: false, nullable: false, diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 03a283cab33b..656ccc7959bd 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -93,7 +93,7 @@ SPDX-License-Identifier: AGPL-3.0-only {{ appearNote.channel.name }} - + @@ -101,7 +101,7 @@ SPDX-License-Identifier: AGPL-3.0-only