mirror of
https://git.joinsharkey.org/Sharkey/Sharkey.git
synced 2024-11-25 06:53:09 +02:00
merge: some future changes coming from upstream (!465)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/465
This commit is contained in:
commit
c67bcad605
60 changed files with 2178 additions and 2093 deletions
|
@ -1,95 +0,0 @@
|
|||
name: Lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- stable
|
||||
- develop
|
||||
paths:
|
||||
- packages/**
|
||||
pull_request:
|
||||
paths:
|
||||
- packages/backend/**
|
||||
- packages/frontend/**
|
||||
- packages/sw/**
|
||||
- packages/misskey-js/**
|
||||
- packages/shared/.eslintrc.js
|
||||
|
||||
jobs:
|
||||
pnpm_install:
|
||||
runs-on: docker
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
- uses: https://github.com/pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
- uses: https://code.forgejo.org/actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
- run: corepack enable
|
||||
- run: pnpm i --frozen-lockfile
|
||||
|
||||
lint:
|
||||
needs: [pnpm_install]
|
||||
runs-on: docker
|
||||
continue-on-error: true
|
||||
strategy:
|
||||
matrix:
|
||||
workspace:
|
||||
- backend
|
||||
- frontend
|
||||
- sw
|
||||
- misskey-js
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
- uses: https://github.com/pnpm/action-setup@v2
|
||||
with:
|
||||
version: 7
|
||||
run_install: false
|
||||
- uses: https://code.forgejo.org/actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
- run: corepack enable
|
||||
- run: pnpm i --frozen-lockfile
|
||||
- run: pnpm --filter ${{ matrix.workspace }} run eslint
|
||||
|
||||
typecheck:
|
||||
needs: [pnpm_install]
|
||||
runs-on: docker
|
||||
continue-on-error: true
|
||||
strategy:
|
||||
matrix:
|
||||
workspace:
|
||||
- backend
|
||||
- misskey-js
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
- uses: https://github.com/pnpm/action-setup@v2
|
||||
with:
|
||||
version: 7
|
||||
run_install: false
|
||||
- uses: https://code.forgejo.org/actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
- run: corepack enable
|
||||
- run: pnpm i --frozen-lockfile
|
||||
- run: pnpm --filter misskey-js run build
|
||||
if: ${{ matrix.workspace == 'backend' }}
|
||||
- run: pnpm --filter misskey-reversi run build:tsc
|
||||
if: ${{ matrix.workspace == 'backend' }}
|
||||
- run: pnpm --filter misskey-bubble-game run build
|
||||
if: ${{ matrix.workspace == 'backend' }}
|
||||
- run: pnpm --filter ${{ matrix.workspace }} run typecheck
|
12
CHANGELOG.md
12
CHANGELOG.md
|
@ -1,17 +1,19 @@
|
|||
<!--
|
||||
## 202x.x.x (unreleased)
|
||||
## Unreleased
|
||||
|
||||
### General
|
||||
-
|
||||
|
||||
### Client
|
||||
-
|
||||
- Enhance: 自分のノートの添付ファイルから直接ファイルの詳細ページに飛べるように
|
||||
- Enhance: 広告がMisskeyと同一ドメインの場合はRouterで遷移するように
|
||||
- Enhance: リアクション・いいねの総数を表示するように
|
||||
- Enhance: リアクション受け入れが「いいねのみ」の場合はリアクション絵文字一覧を表示しないように
|
||||
- Fix: 一部のページ内リンクが正しく動作しない問題を修正
|
||||
- Fix: 周年の実績が閏年を考慮しない問題を修正
|
||||
|
||||
### Server
|
||||
-
|
||||
|
||||
-->
|
||||
|
||||
## 2024.3.1
|
||||
|
||||
### General
|
||||
|
|
|
@ -307,6 +307,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>(FooService);
|
||||
barService = app.get<BarService>(BarService) as jest.Mocked<BarService>;
|
||||
|
||||
// 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する
|
||||
|
|
10
locales/index.d.ts
vendored
10
locales/index.d.ts
vendored
|
@ -2044,6 +2044,10 @@ export interface Locale extends ILocale {
|
|||
* ノートのアクションをホバー時のみ表示する
|
||||
*/
|
||||
"showNoteActionsOnlyHover": string;
|
||||
/**
|
||||
* ノートのリアクション数を表示する
|
||||
*/
|
||||
"showReactionsCount": string;
|
||||
/**
|
||||
* 履歴はありません
|
||||
*/
|
||||
|
@ -9167,7 +9171,11 @@ export interface Locale extends ILocale {
|
|||
*/
|
||||
"reactedBySomeUsers": ParameterizedString<"n">;
|
||||
/**
|
||||
* {n}人がブーストしました
|
||||
* {n}人がいいねしました
|
||||
*/
|
||||
"likedBySomeUsers": ParameterizedString<"n">;
|
||||
/**
|
||||
* {n}人がリノートしました
|
||||
*/
|
||||
"renotedBySomeUsers": ParameterizedString<"n">;
|
||||
/**
|
||||
|
|
|
@ -507,6 +507,7 @@ emojiStyle: "絵文字のスタイル"
|
|||
native: "ネイティブ"
|
||||
disableDrawer: "メニューをドロワーで表示しない"
|
||||
showNoteActionsOnlyHover: "ノートのアクションをホバー時のみ表示する"
|
||||
showReactionsCount: "ノートのリアクション数を表示する"
|
||||
noHistory: "履歴はありません"
|
||||
signinHistory: "ログイン履歴"
|
||||
enableAdvancedMfm: "高度なMFMを有効にする"
|
||||
|
@ -2419,7 +2420,8 @@ _notification:
|
|||
sendTestNotification: "テスト通知を送信する"
|
||||
notificationWillBeDisplayedLikeThis: "通知はこのように表示されます"
|
||||
reactedBySomeUsers: "{n}人がリアクションしました"
|
||||
renotedBySomeUsers: "{n}人がブーストしました"
|
||||
likedBySomeUsers: "{n}人がいいねしました"
|
||||
renotedBySomeUsers: "{n}人がリノートしました"
|
||||
followedBySomeUsers: "{n}人にフォローされました"
|
||||
flushNotification: "通知の履歴をリセットする"
|
||||
|
||||
|
|
|
@ -51,21 +51,35 @@ export class FetchInstanceMetadataService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public async tryLock(host: string): Promise<boolean> {
|
||||
const mutex = await this.redisClient.set(`fetchInstanceMetadata:mutex:${host}`, '1', 'GET');
|
||||
return mutex !== '1';
|
||||
// public for test
|
||||
public async tryLock(host: string): Promise<string | null> {
|
||||
// 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<number> {
|
||||
return this.redisClient.del(`fetchInstanceMetadata:mutex:v2:${host}`);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async fetchInstanceMetadata(instance: MiInstance, force = false): Promise<void> {
|
||||
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);
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -351,6 +351,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,
|
||||
|
|
|
@ -223,6 +223,10 @@ export const packedNoteSchema = {
|
|||
}],
|
||||
},
|
||||
},
|
||||
reactionCount: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
renoteCount: {
|
||||
type: 'number',
|
||||
optional: false, nullable: false,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<Note> => post(alice, { text: `${keyword}` }), included: true },
|
||||
{ note: (): Promise<Note> => 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<Note> => post(alice, { text: `${keyword}` }), included: true },
|
||||
{ note: (): Promise<Note> => 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<Note> => post(userFollowedByAlice, { text: `${keyword}`, visibility: 'public' }), included: true },
|
||||
{ note: (): Promise<Note> => post(userFollowedByAlice, { text: `${keyword}`, visibility: 'home' }), included: true },
|
||||
|
@ -367,56 +363,56 @@ describe('アンテナ', () => {
|
|||
},
|
||||
{
|
||||
label: 'ブロックしているユーザーのノートは含む',
|
||||
parameters: (): object => ({}),
|
||||
parameters: () => ({}),
|
||||
posts: [
|
||||
{ note: (): Promise<Note> => post(userBlockedByAlice, { text: `${keyword}` }), included: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'ブロックされているユーザーのノートは含まない',
|
||||
parameters: (): object => ({}),
|
||||
parameters: () => ({}),
|
||||
posts: [
|
||||
{ note: (): Promise<Note> => post(userBlockingAlice, { text: `${keyword}` }) },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'ミュートしているユーザーのノートは含まない',
|
||||
parameters: (): object => ({}),
|
||||
parameters: () => ({}),
|
||||
posts: [
|
||||
{ note: (): Promise<Note> => post(userMutedByAlice, { text: `${keyword}` }) },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'ミュートされているユーザーのノートは含む',
|
||||
parameters: (): object => ({}),
|
||||
parameters: () => ({}),
|
||||
posts: [
|
||||
{ note: (): Promise<Note> => post(userMutingAlice, { text: `${keyword}` }), included: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '「見つけやすくする」がOFFのユーザーのノートも含まれる',
|
||||
parameters: (): object => ({}),
|
||||
parameters: () => ({}),
|
||||
posts: [
|
||||
{ note: (): Promise<Note> => post(userNotExplorable, { text: `${keyword}` }), included: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '鍵付きユーザーのノートも含まれる',
|
||||
parameters: (): object => ({}),
|
||||
parameters: () => ({}),
|
||||
posts: [
|
||||
{ note: (): Promise<Note> => post(userLocking, { text: `${keyword}` }), included: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'サイレンスのノートも含まれる',
|
||||
parameters: (): object => ({}),
|
||||
parameters: () => ({}),
|
||||
posts: [
|
||||
{ note: (): Promise<Note> => post(userSilenced, { text: `${keyword}` }), included: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '削除ユーザーのノートも含まれる',
|
||||
parameters: (): object => ({}),
|
||||
parameters: () => ({}),
|
||||
posts: [
|
||||
{ note: (): Promise<Note> => post(userDeletedBySelf, { text: `${keyword}` }), included: true },
|
||||
{ note: (): Promise<Note> => 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<Note> => post(alice, { text: `test ${keyword}` }) },
|
||||
{ note: (): Promise<Note> => 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<Note> => post(alice, { text: `test ${keyword}` }) },
|
||||
{ note: (): Promise<Note> => post(bob, { text: `test ${keyword}` }), included: true },
|
||||
|
@ -442,14 +438,14 @@ describe('アンテナ', () => {
|
|||
},
|
||||
{
|
||||
label: 'CWにもマッチする',
|
||||
parameters: (): object => ({ keywords: [[keyword]] }),
|
||||
parameters: () => ({ keywords: [[keyword]] }),
|
||||
posts: [
|
||||
{ note: (): Promise<Note> => post(bob, { text: 'test', cw: `cw ${keyword}` }), included: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'キーワード1つ',
|
||||
parameters: (): object => ({ keywords: [[keyword]] }),
|
||||
parameters: () => ({ keywords: [[keyword]] }),
|
||||
posts: [
|
||||
{ note: (): Promise<Note> => post(alice, { text: 'test' }) },
|
||||
{ note: (): Promise<Note> => 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<Note> => post(bob, { text: 'test A' }) },
|
||||
{ note: (): Promise<Note> => 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<Note> => post(bob, { text: 'test' }) },
|
||||
{ note: (): Promise<Note> => 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<Note> => post(bob, { text: `test ${keyword}` }), included: true },
|
||||
{ note: (): Promise<Note> => 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<Note> => post(bob, { text: `test ${keyword}` }), included: true },
|
||||
{ note: (): Promise<Note> => 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<Note> => post(bob, { text: 'keyword' }) },
|
||||
{ note: (): Promise<Note> => 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<Note> => post(bob, { text: 'keyword' }), included: true },
|
||||
{ note: (): Promise<Note> => 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<Note> => post(bob, { text: `${keyword}` }), included: true },
|
||||
{ note: (): Promise<Note> => 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<Note> => post(bob, { text: `${keyword}` }), included: true },
|
||||
{ note: (): Promise<Note> => post(bob, { text: `${keyword} keyword` }) },
|
||||
|
@ -546,7 +542,7 @@ describe('アンテナ', () => {
|
|||
},
|
||||
{
|
||||
label: '添付ファイルを問わない',
|
||||
parameters: (): object => ({ withFile: false }),
|
||||
parameters: () => ({ withFile: false }),
|
||||
posts: [
|
||||
{ note: (): Promise<Note> => post(bob, { text: `${keyword}`, fileIds: [bobFile.id] }), included: true },
|
||||
{ note: (): Promise<Note> => post(bob, { text: `${keyword}` }), included: true },
|
||||
|
@ -554,7 +550,7 @@ describe('アンテナ', () => {
|
|||
},
|
||||
{
|
||||
label: '添付ファイル付きのみ',
|
||||
parameters: (): object => ({ withFile: true }),
|
||||
parameters: () => ({ withFile: true }),
|
||||
posts: [
|
||||
{ note: (): Promise<Note> => post(bob, { text: `${keyword}`, fileIds: [bobFile.id] }), included: true },
|
||||
{ note: (): Promise<Note> => post(bob, { text: `${keyword}` }) },
|
||||
|
@ -562,7 +558,7 @@ describe('アンテナ', () => {
|
|||
},
|
||||
{
|
||||
label: 'リプライ以外',
|
||||
parameters: (): object => ({ withReplies: false }),
|
||||
parameters: () => ({ withReplies: false }),
|
||||
posts: [
|
||||
{ note: (): Promise<Note> => post(bob, { text: `${keyword}`, replyId: alicePost.id }) },
|
||||
{ note: (): Promise<Note> => post(bob, { text: `${keyword}` }), included: true },
|
||||
|
@ -570,7 +566,7 @@ describe('アンテナ', () => {
|
|||
},
|
||||
{
|
||||
label: 'リプライも含む',
|
||||
parameters: (): object => ({ withReplies: true }),
|
||||
parameters: () => ({ withReplies: true }),
|
||||
posts: [
|
||||
{ note: (): Promise<Note> => post(bob, { text: `${keyword}`, replyId: alicePost.id }), included: true },
|
||||
{ note: (): Promise<Note> => 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');
|
||||
});
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
|
||||
|
||||
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 = <T extends { id: string }, >(selector: (s: T) => string = (s: T): string => s.id) => (a: T, b: T): number => {
|
||||
return selector(a).localeCompare(selector(b));
|
||||
};
|
||||
|
||||
type CreateParam = JTDDataType<typeof CreateParamDef>;
|
||||
const defaultCreate = (): Partial<CreateParam> => ({
|
||||
const defaultCreate = (): Pick<Misskey.entities.ClipsCreateRequest, 'name'> => ({
|
||||
name: 'test',
|
||||
});
|
||||
const create = async (parameters: Partial<CreateParam> = {}, request: Partial<ApiRequest> = {}): Promise<Clip> => {
|
||||
const clip = await successfulApiCall<Clip>({
|
||||
endpoint: '/clips/create',
|
||||
const create = async (parameters: Partial<Misskey.entities.ClipsCreateRequest> = {}, request: Partial<ApiRequest<'clips/create'>> = {}): Promise<Misskey.entities.Clip> => {
|
||||
const clip = await successfulApiCall({
|
||||
endpoint: 'clips/create',
|
||||
parameters: {
|
||||
...defaultCreate(),
|
||||
...parameters,
|
||||
|
@ -64,17 +51,16 @@ describe('クリップ', () => {
|
|||
return clip;
|
||||
};
|
||||
|
||||
const createMany = async (parameters: Partial<CreateParam>, count = 10, user = alice): Promise<Clip[]> => {
|
||||
const createMany = async (parameters: Partial<Misskey.entities.ClipsCreateRequest>, count = 10, user = alice): Promise<Misskey.entities.Clip[]> => {
|
||||
return await Promise.all([...Array(count)].map((_, i) => create({
|
||||
name: `test${i}`,
|
||||
...parameters,
|
||||
}, { user })));
|
||||
};
|
||||
|
||||
type UpdateParam = JTDDataType<typeof UpdateParamDef>;
|
||||
const update = async (parameters: Partial<UpdateParam>, request: Partial<ApiRequest> = {}): Promise<Clip> => {
|
||||
const clip = await successfulApiCall<Clip>({
|
||||
endpoint: '/clips/update',
|
||||
const update = async (parameters: Optional<Misskey.entities.ClipsUpdateRequest, 'name'>, request: Partial<ApiRequest<'clips/update'>> = {}): Promise<Misskey.entities.Clip> => {
|
||||
const clip = await successfulApiCall({
|
||||
endpoint: 'clips/update',
|
||||
parameters: {
|
||||
name: 'updated',
|
||||
...parameters,
|
||||
|
@ -92,41 +78,39 @@ describe('クリップ', () => {
|
|||
return clip;
|
||||
};
|
||||
|
||||
type DeleteParam = JTDDataType<typeof DeleteParamDef>;
|
||||
const deleteClip = async (parameters: DeleteParam, request: Partial<ApiRequest> = {}): Promise<void> => {
|
||||
return await successfulApiCall<void>({
|
||||
endpoint: '/clips/delete',
|
||||
const deleteClip = async (parameters: Misskey.entities.ClipsDeleteRequest, request: Partial<ApiRequest<'clips/delete'>> = {}): Promise<void> => {
|
||||
return await successfulApiCall({
|
||||
endpoint: 'clips/delete',
|
||||
parameters,
|
||||
user: alice,
|
||||
...request,
|
||||
}, {
|
||||
status: 204,
|
||||
});
|
||||
}) as any as void;
|
||||
};
|
||||
|
||||
type ShowParam = JTDDataType<typeof ShowParamDef>;
|
||||
const show = async (parameters: ShowParam, request: Partial<ApiRequest> = {}): Promise<Clip> => {
|
||||
return await successfulApiCall<Clip>({
|
||||
endpoint: '/clips/show',
|
||||
const show = async (parameters: Misskey.entities.ClipsShowRequest, request: Partial<ApiRequest<'clips/show'>> = {}): Promise<Misskey.entities.Clip> => {
|
||||
return await successfulApiCall({
|
||||
endpoint: 'clips/show',
|
||||
parameters,
|
||||
user: alice,
|
||||
...request,
|
||||
});
|
||||
};
|
||||
|
||||
const list = async (request: Partial<ApiRequest>): Promise<Clip[]> => {
|
||||
return successfulApiCall<Clip[]>({
|
||||
endpoint: '/clips/list',
|
||||
const list = async (request: Partial<ApiRequest<'clips/list'>>): Promise<Misskey.entities.Clip[]> => {
|
||||
return successfulApiCall({
|
||||
endpoint: 'clips/list',
|
||||
parameters: {},
|
||||
user: alice,
|
||||
...request,
|
||||
});
|
||||
};
|
||||
|
||||
const usersClips = async (request: Partial<ApiRequest>): Promise<Clip[]> => {
|
||||
return await successfulApiCall<Clip[]>({
|
||||
endpoint: '/users/clips',
|
||||
parameters: {},
|
||||
const usersClips = async (parameters: Misskey.entities.UsersClipsRequest, request: Partial<ApiRequest<'users/clips'>> = {}): Promise<Misskey.entities.Clip[]> => {
|
||||
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,
|
||||
},
|
||||
});
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
// 返ってくる配列には順序保障がないのでidでソートして厳密比較
|
||||
assert.deepStrictEqual(
|
||||
res.sort(compareBy<Clip>(s => s.id)),
|
||||
res.sort(compareBy<Misskey.entities.Clip>(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))(),
|
||||
}, {
|
||||
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,
|
||||
},
|
||||
});
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
// Promise.allで返ってくる配列には順序保障がないのでidでソートして厳密比較
|
||||
assert.deepStrictEqual(
|
||||
res.sort(compareBy<Clip>(s => s.id)),
|
||||
res.sort(compareBy<Misskey.entities.Clip>(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<typeof FavoriteParamDef>;
|
||||
const favorite = async (parameters: FavoriteParam, request: Partial<ApiRequest> = {}): Promise<void> => {
|
||||
return successfulApiCall<void>({
|
||||
endpoint: '/clips/favorite',
|
||||
const favorite = async (parameters: Misskey.entities.ClipsFavoriteRequest, request: Partial<ApiRequest<'clips/favorite'>> = {}): Promise<void> => {
|
||||
return successfulApiCall({
|
||||
endpoint: 'clips/favorite',
|
||||
parameters,
|
||||
user: alice,
|
||||
...request,
|
||||
}, {
|
||||
status: 204,
|
||||
});
|
||||
}) as any as void;
|
||||
};
|
||||
|
||||
type UnfavoriteParam = JTDDataType<typeof UnfavoriteParamDef>;
|
||||
const unfavorite = async (parameters: UnfavoriteParam, request: Partial<ApiRequest> = {}): Promise<void> => {
|
||||
return successfulApiCall<void>({
|
||||
endpoint: '/clips/unfavorite',
|
||||
const unfavorite = async (parameters: Misskey.entities.ClipsUnfavoriteRequest, request: Partial<ApiRequest<'clips/unfavorite'>> = {}): Promise<void> => {
|
||||
return successfulApiCall({
|
||||
endpoint: 'clips/unfavorite',
|
||||
parameters,
|
||||
user: alice,
|
||||
...request,
|
||||
}, {
|
||||
status: 204,
|
||||
});
|
||||
}) as any as void;
|
||||
};
|
||||
|
||||
const myFavorites = async (request: Partial<ApiRequest> = {}): Promise<Clip[]> => {
|
||||
return successfulApiCall<Clip[]>({
|
||||
endpoint: '/clips/my-favorites',
|
||||
const myFavorites = async (request: Partial<ApiRequest<'clips/my-favorites'>> = {}): Promise<Misskey.entities.Clip[]> => {
|
||||
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<typeof AddNoteParamDef>;
|
||||
const addNote = async (parameters: AddNoteParam, request: Partial<ApiRequest> = {}): Promise<void> => {
|
||||
return successfulApiCall<void>({
|
||||
endpoint: '/clips/add-note',
|
||||
const addNote = async (parameters: Misskey.entities.ClipsAddNoteRequest, request: Partial<ApiRequest<'clips/add-note'>> = {}): Promise<void> => {
|
||||
return successfulApiCall({
|
||||
endpoint: 'clips/add-note',
|
||||
parameters,
|
||||
user: alice,
|
||||
...request,
|
||||
}, {
|
||||
status: 204,
|
||||
});
|
||||
}) as any as void;
|
||||
};
|
||||
|
||||
type RemoveNoteParam = JTDDataType<typeof RemoveNoteParamDef>;
|
||||
const removeNote = async (parameters: RemoveNoteParam, request: Partial<ApiRequest> = {}): Promise<void> => {
|
||||
return successfulApiCall<void>({
|
||||
endpoint: '/clips/remove-note',
|
||||
const removeNote = async (parameters: Misskey.entities.ClipsRemoveNoteRequest, request: Partial<ApiRequest<'clips/remove-note'>> = {}): Promise<void> => {
|
||||
return successfulApiCall({
|
||||
endpoint: 'clips/remove-note',
|
||||
parameters,
|
||||
user: alice,
|
||||
...request,
|
||||
}, {
|
||||
status: 204,
|
||||
});
|
||||
}) as any as void;
|
||||
};
|
||||
|
||||
type NotesParam = JTDDataType<typeof NotesParamDef>;
|
||||
const notes = async (parameters: Partial<NotesParam>, request: Partial<ApiRequest> = {}): Promise<Note[]> => {
|
||||
return successfulApiCall<Note[]>({
|
||||
endpoint: '/clips/notes',
|
||||
const notes = async (parameters: Misskey.entities.ClipsNotesRequest, request: Partial<ApiRequest<'clips/notes'>> = {}): Promise<Misskey.entities.Note[]> => {
|
||||
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',
|
||||
|
|
|
@ -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<MiNote>;
|
||||
|
||||
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);
|
||||
|
||||
})
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -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<string> => {
|
||||
const getWebpublicType = async (user: misskey.entities.SignupResponse, fileId: string): Promise<string> => {
|
||||
// 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),
|
||||
]);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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, {});
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 () => {
|
||||
|
@ -472,7 +472,7 @@ describe('Note', () => {
|
|||
priority: 0,
|
||||
value: true,
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -783,7 +784,7 @@ describe('Note', () => {
|
|||
priority: 1,
|
||||
value: 0,
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -837,7 +838,7 @@ describe('Note', () => {
|
|||
priority: 1,
|
||||
value: 0,
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
|
@ -853,7 +854,7 @@ 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],
|
||||
|
@ -893,7 +894,7 @@ describe('Note', () => {
|
|||
priority: 1,
|
||||
value: 1,
|
||||
},
|
||||
},
|
||||
} as any,
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
|
@ -909,7 +910,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',
|
||||
visibility: 'specified',
|
||||
visibleUserIds: [bob.id],
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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<void>(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);
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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);
|
||||
|
|
|
@ -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<MeDetailed | UserDetailedNotMe> => {
|
||||
return successfulApiCall({ endpoint: 'users/show', parameters: { userId: id }, user: me }) as any;
|
||||
const show = async (id: string, me = root): Promise<misskey.entities.UserDetailed> => {
|
||||
return successfulApiCall({ endpoint: 'users/show', parameters: { userId: id }, user: me });
|
||||
};
|
||||
|
||||
// UserLiteのキーが過不足なく入っている?
|
||||
const userLite = (user: User): Partial<UserLite> => {
|
||||
const userLite = (user: misskey.entities.UserLite): Partial<misskey.entities.UserLite> => {
|
||||
return stripUndefined({
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
|
@ -72,7 +53,7 @@ describe('ユーザー', () => {
|
|||
};
|
||||
|
||||
// UserDetailedNotMeのキーが過不足なく入っている?
|
||||
const userDetailedNotMe = (user: User): Partial<UserDetailedNotMe> => {
|
||||
const userDetailedNotMe = (user: misskey.entities.SignupResponse): Partial<misskey.entities.UserDetailedNotMe> => {
|
||||
return stripUndefined({
|
||||
...userLite(user),
|
||||
url: user.url,
|
||||
|
@ -114,7 +95,7 @@ describe('ユーザー', () => {
|
|||
};
|
||||
|
||||
// Relations関連のキーが過不足なく入っている?
|
||||
const userDetailedNotMeWithRelations = (user: User): Partial<UserDetailedNotMe> => {
|
||||
const userDetailedNotMeWithRelations = (user: misskey.entities.SignupResponse): Partial<misskey.entities.UserDetailedNotMe> => {
|
||||
return stripUndefined({
|
||||
...userDetailedNotMe(user),
|
||||
isFollowing: user.isFollowing ?? false,
|
||||
|
@ -131,7 +112,7 @@ describe('ユーザー', () => {
|
|||
};
|
||||
|
||||
// MeDetailedのキーが過不足なく入っている?
|
||||
const meDetailed = (user: User, security = false): Partial<MeDetailed> => {
|
||||
const meDetailed = (user: misskey.entities.SignupResponse, security = false): Partial<misskey.entities.MeDetailed> => {
|
||||
return stripUndefined({
|
||||
...userDetailedNotMe(user),
|
||||
avatarId: user.avatarId,
|
||||
|
@ -162,6 +143,7 @@ describe('ユーザー', () => {
|
|||
mutedWords: user.mutedWords,
|
||||
hardMutedWords: user.hardMutedWords,
|
||||
mutedInstances: user.mutedInstances,
|
||||
// @ts-expect-error 後方互換性
|
||||
mutingNotificationTypes: user.mutingNotificationTypes,
|
||||
notificationRecieveConfig: user.notificationRecieveConfig,
|
||||
emailNotificationTypes: user.emailNotificationTypes,
|
||||
|
@ -176,61 +158,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 bob: misskey.entities.SignupResponse;
|
||||
|
||||
let carol: User;
|
||||
let dave: User;
|
||||
let ellen: User;
|
||||
let frank: User;
|
||||
// NOTE: これがないと落ちる(bob の updatedAt が null になってしまうため?)
|
||||
let bobNote: misskey.entities.Note; // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
|
||||
let usersReplying: User[];
|
||||
let carol: misskey.entities.SignupResponse;
|
||||
|
||||
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 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) => {
|
||||
|
@ -241,7 +215,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' });
|
||||
|
@ -309,7 +283,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 });
|
||||
});
|
||||
|
@ -322,7 +296,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}/);
|
||||
|
@ -332,7 +306,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);
|
||||
|
@ -407,6 +381,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']);
|
||||
|
@ -436,68 +411,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 => ({ speakAsCat: true }) },
|
||||
{ parameters: (): object => ({ speakAsCat: 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() };
|
||||
|
@ -506,13 +479,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,
|
||||
};
|
||||
|
@ -531,13 +504,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,
|
||||
};
|
||||
|
@ -612,13 +585,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 });
|
||||
|
||||
|
@ -631,15 +604,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 });
|
||||
|
@ -653,39 +626,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 });
|
||||
|
@ -727,17 +705,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 });
|
||||
|
@ -762,15 +741,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 });
|
||||
|
@ -784,30 +763,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 });
|
||||
|
@ -829,15 +808,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 });
|
||||
|
@ -851,12 +830,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 });
|
||||
|
@ -870,15 +849,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) {
|
||||
|
|
7
packages/backend/test/global.d.ts
vendored
Normal file
7
packages/backend/test/global.d.ts
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type FIXME = any;
|
|
@ -4,10 +4,10 @@
|
|||
*/
|
||||
|
||||
import Ajv from 'ajv';
|
||||
import { Schema } from '@/misc/schema';
|
||||
import { Schema } from '@/misc/json-schema.js';
|
||||
|
||||
export const getValidator = (paramDef: Schema) => {
|
||||
const ajv = new Ajv({
|
||||
const ajv = new Ajv.default({
|
||||
useDefaults: true,
|
||||
});
|
||||
ajv.addFormat('misskey:id', /^[a-zA-Z0-9]+$/);
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"noUnusedParameters": false,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedLocals": false,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"declaration": false,
|
||||
"sourceMap": true,
|
||||
|
@ -18,6 +18,7 @@
|
|||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"strictPropertyInitialization": false,
|
||||
"skipLibCheck": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"resolveJsonModule": true,
|
||||
|
|
|
@ -51,7 +51,7 @@ describe('AnnouncementService', () => {
|
|||
|
||||
function createAnnouncement(data: Partial<MiAnnouncement & { createdAt: Date }> = {}) {
|
||||
return announcementsRepository.insert({
|
||||
id: genAidx(data.createdAt ?? new Date()),
|
||||
id: genAidx(data.createdAt?.getTime() ?? Date.now()),
|
||||
updatedAt: null,
|
||||
title: 'Title',
|
||||
text: 'Text',
|
||||
|
|
|
@ -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;
|
||||
|
@ -56,12 +56,13 @@ describe('FetchInstanceMetadataService', () => {
|
|||
} else if (token === DI.redis) {
|
||||
return mockRedis;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.compile();
|
||||
|
||||
app.enableShutdownHooks();
|
||||
|
||||
fetchInstanceMetadataService = app.get<FetchInstanceMetadataService>(FetchInstanceMetadataService);
|
||||
fetchInstanceMetadataService = app.get<FetchInstanceMetadataService>(FetchInstanceMetadataService) as jest.Mocked<FetchInstanceMetadataService>;
|
||||
federatedInstanceService = app.get<FederatedInstanceService>(FederatedInstanceService) as jest.Mocked<FederatedInstanceService>;
|
||||
redisClient = app.get<Redis>(DI.redis) as jest.Mocked<Redis>;
|
||||
httpRequestService = app.get<HttpRequestService>(HttpRequestService) as jest.Mocked<HttpRequestService>;
|
||||
|
@ -74,11 +75,12 @@ 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 +90,12 @@ 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,15 +104,33 @@ 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(); });
|
||||
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' });
|
||||
expect(tryLockSpy).toHaveBeenCalledTimes(2);
|
||||
|
||||
await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any);
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -90,7 +90,8 @@ describe('RelayService', () => {
|
|||
|
||||
expect(queueService.deliver).toHaveBeenCalled();
|
||||
expect(queueService.deliver.mock.lastCall![1]?.type).toBe('Undo');
|
||||
expect(queueService.deliver.mock.lastCall![1]?.object.type).toBe('Follow');
|
||||
expect(typeof queueService.deliver.mock.lastCall![1]?.object).toBe('object');
|
||||
expect((queueService.deliver.mock.lastCall![1]?.object as any).type).toBe('Follow');
|
||||
expect(queueService.deliver.mock.lastCall![2]).toBe('https://example.com');
|
||||
//expect(queueService.deliver.mock.lastCall![0].username).toBe('relay.actor');
|
||||
|
||||
|
|
|
@ -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,
|
||||
}],
|
||||
|
|
|
@ -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<E extends keyof misskey.Endpoints, P extends misskey.Endpoints[E]['req'] = misskey.Endpoints[E]['req']> = {
|
||||
endpoint: E,
|
||||
parameters: P,
|
||||
user: UserToken | undefined,
|
||||
};
|
||||
|
||||
export const successfulApiCall = async <T, >(request: ApiRequest, assertion: {
|
||||
export const successfulApiCall = async <E extends keyof misskey.Endpoints, P extends misskey.Endpoints[E]['req']>(request: ApiRequest<E, P>, assertion: {
|
||||
status?: number,
|
||||
} = {}): Promise<T> => {
|
||||
} = {}): Promise<misskey.api.SwitchCaseResponseType<E, P>> => {
|
||||
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 <T, >(request: ApiRequest, assertion: {
|
|||
return res.body;
|
||||
};
|
||||
|
||||
export const failedApiCall = async <T, >(request: ApiRequest, assertion: {
|
||||
export const failedApiCall = async <T, E extends keyof misskey.Endpoints, P extends misskey.Endpoints[E]['req']>(request: ApiRequest<E, P>, assertion: {
|
||||
status: number,
|
||||
code: string,
|
||||
id: string
|
||||
|
@ -70,7 +64,7 @@ export const failedApiCall = async <T, >(request: ApiRequest, assertion: {
|
|||
return res.body;
|
||||
};
|
||||
|
||||
const request = async (path: string, params: any, me?: UserToken): Promise<{
|
||||
export const api = async <E extends keyof misskey.Endpoints>(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<misskey.Endpoints['signup']['req']
|
|||
return res.body;
|
||||
};
|
||||
|
||||
export const post = async (user: UserToken, params?: misskey.Endpoints['notes/create']['req']): Promise<misskey.entities.Note> => {
|
||||
export const post = async (user: UserToken, params: misskey.Endpoints['notes/create']['req']): Promise<misskey.entities.Note> => {
|
||||
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<any> => {
|
||||
export const react = async (user: UserToken, note: misskey.entities.Note, reaction: string): Promise<void> => {
|
||||
await api('notes/reactions/create', {
|
||||
noteId: note.id,
|
||||
reaction: reaction,
|
||||
}, user);
|
||||
};
|
||||
|
||||
export const userList = async (user: UserToken, userList: any = {}): Promise<any> => {
|
||||
export const userList = async (user: UserToken, userList: Partial<misskey.entities.UserList> = {}): Promise<misskey.entities.UserList> => {
|
||||
const res = await api('users/lists/create', {
|
||||
name: 'test',
|
||||
...userList,
|
||||
}, user);
|
||||
return res.body;
|
||||
};
|
||||
|
||||
export const page = async (user: UserToken, page: any = {}): Promise<any> => {
|
||||
export const page = async (user: UserToken, page: Partial<misskey.entities.Page> = {}): Promise<misskey.entities.Page> => {
|
||||
const res = await api('pages/create', {
|
||||
alignCenter: false,
|
||||
content: [
|
||||
|
@ -198,7 +193,7 @@ export const page = async (user: UserToken, page: any = {}): Promise<any> => {
|
|||
},
|
||||
],
|
||||
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<any> => {
|
|||
return res.body;
|
||||
};
|
||||
|
||||
export const play = async (user: UserToken, play: any = {}): Promise<any> => {
|
||||
export const play = async (user: UserToken, play: Partial<misskey.entities.Flash> = {}): Promise<misskey.entities.Flash> => {
|
||||
const res = await api('flash/create', {
|
||||
permissions: [],
|
||||
script: 'test',
|
||||
|
@ -221,7 +216,7 @@ export const play = async (user: UserToken, play: any = {}): Promise<any> => {
|
|||
return res.body;
|
||||
};
|
||||
|
||||
export const clip = async (user: UserToken, clip: any = {}): Promise<any> => {
|
||||
export const clip = async (user: UserToken, clip: Partial<misskey.entities.Clip> = {}): Promise<misskey.entities.Clip> => {
|
||||
const res = await api('clips/create', {
|
||||
description: null,
|
||||
isPublic: true,
|
||||
|
@ -231,18 +226,18 @@ export const clip = async (user: UserToken, clip: any = {}): Promise<any> => {
|
|||
return res.body;
|
||||
};
|
||||
|
||||
export const galleryPost = async (user: UserToken, channel: any = {}): Promise<any> => {
|
||||
export const galleryPost = async (user: UserToken, galleryPost: Partial<misskey.entities.GalleryPost> = {}): Promise<misskey.entities.GalleryPost> => {
|
||||
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<any> => {
|
||||
export const channel = async (user: UserToken, channel: Partial<misskey.entities.Channel> = {}): Promise<misskey.entities.Channel> => {
|
||||
const res = await api('channels/create', {
|
||||
bannerId: null,
|
||||
description: null,
|
||||
|
@ -252,7 +247,7 @@ export const channel = async (user: UserToken, channel: any = {}): Promise<any>
|
|||
return res.body;
|
||||
};
|
||||
|
||||
export const role = async (user: UserToken, role: any = {}, policies: any = {}): Promise<any> => {
|
||||
export const role = async (user: UserToken, role: Partial<misskey.entities.Role> = {}, policies: any = {}): Promise<misskey.entities.Role> => {
|
||||
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<Packed<'DriveFile'>> => {
|
||||
export const uploadUrl = async (user: UserToken, url: string): Promise<misskey.entities.DriveFile> => {
|
||||
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,
|
||||
);
|
||||
|
||||
|
|
|
@ -189,14 +189,26 @@ export async function mainBoot() {
|
|||
if ($i.followersCount >= 500) claimAchievement('followers500');
|
||||
if ($i.followersCount >= 1000) claimAchievement('followers1000');
|
||||
|
||||
if (Date.now() - new Date($i.createdAt).getTime() > 1000 * 60 * 60 * 24 * 365) {
|
||||
const createdAt = new Date($i.createdAt);
|
||||
const createdAtThreeYearsLater = new Date($i.createdAt);
|
||||
createdAtThreeYearsLater.setFullYear(createdAtThreeYearsLater.getFullYear() + 3);
|
||||
if (now >= createdAtThreeYearsLater) {
|
||||
claimAchievement('passedSinceAccountCreated3');
|
||||
claimAchievement('passedSinceAccountCreated2');
|
||||
claimAchievement('passedSinceAccountCreated1');
|
||||
} else {
|
||||
const createdAtTwoYearsLater = new Date($i.createdAt);
|
||||
createdAtTwoYearsLater.setFullYear(createdAtTwoYearsLater.getFullYear() + 2);
|
||||
if (now >= createdAtTwoYearsLater) {
|
||||
claimAchievement('passedSinceAccountCreated2');
|
||||
claimAchievement('passedSinceAccountCreated1');
|
||||
} else {
|
||||
const createdAtOneYearLater = new Date($i.createdAt);
|
||||
createdAtOneYearLater.setFullYear(createdAtOneYearLater.getFullYear() + 1);
|
||||
if (now >= createdAtOneYearLater) {
|
||||
claimAchievement('passedSinceAccountCreated1');
|
||||
}
|
||||
if (Date.now() - new Date($i.createdAt).getTime() > 1000 * 60 * 60 * 24 * 365 * 2) {
|
||||
claimAchievement('passedSinceAccountCreated2');
|
||||
}
|
||||
if (Date.now() - new Date($i.createdAt).getTime() > 1000 * 60 * 60 * 24 * 365 * 3) {
|
||||
claimAchievement('passedSinceAccountCreated3');
|
||||
}
|
||||
|
||||
if (claimedAchievements.length >= 30) {
|
||||
|
@ -231,7 +243,7 @@ export async function mainBoot() {
|
|||
|
||||
const latestDonationInfoShownAt = miLocalStorage.getItem('latestDonationInfoShownAt');
|
||||
const neverShowDonationInfo = miLocalStorage.getItem('neverShowDonationInfo');
|
||||
if (neverShowDonationInfo !== 'true' && (new Date($i.createdAt).getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 3))) && !location.pathname.startsWith('/miauth')) {
|
||||
if (neverShowDonationInfo !== 'true' && (createdAt.getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 3))) && !location.pathname.startsWith('/miauth')) {
|
||||
if (latestDonationInfoShownAt == null || (new Date(latestDonationInfoShownAt).getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 30)))) {
|
||||
popup(defineAsyncComponent(() => import('@/components/MkDonation.vue')), {}, {}, 'closed');
|
||||
}
|
||||
|
|
|
@ -69,7 +69,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;
|
||||
|
@ -99,8 +99,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 ? 'ph-eye ph-bold ph-lg' : 'ph-eye-slash ph-bold ph-lg',
|
||||
danger: true,
|
||||
|
@ -108,6 +106,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',
|
||||
|
|
|
@ -60,7 +60,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;
|
||||
|
@ -115,6 +115,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);
|
||||
}
|
||||
|
||||
|
|
|
@ -97,7 +97,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;
|
||||
|
@ -125,8 +125,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 ? 'ph-eye ph-bold ph-lg' : 'ph-eye-slash ph-bold ph-lg',
|
||||
danger: true,
|
||||
|
@ -134,6 +132,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',
|
||||
|
|
|
@ -97,7 +97,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
<MkA v-if="appearNote.channel && !inChannel" :class="$style.channel" :to="`/channels/${appearNote.channel.id}`"><i class="ph-television ph-bold ph-lg"></i> {{ appearNote.channel.name }}</MkA>
|
||||
</div>
|
||||
<MkReactionsViewer :note="appearNote" :maxNumber="16" @click.stop @mockUpdateMyReaction="emitUpdReaction">
|
||||
<MkReactionsViewer v-if="appearNote.reactionAcceptance !== 'likeOnly'" :note="appearNote" :maxNumber="16" @click.stop @mockUpdateMyReaction="emitUpdReaction">
|
||||
<template #more>
|
||||
<div :class="$style.reactionOmitted">{{ i18n.ts.more }}</div>
|
||||
</template>
|
||||
|
@ -105,7 +105,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<footer :class="$style.footer">
|
||||
<button :class="$style.footerButton" class="_button" @click.stop @click="reply()">
|
||||
<i class="ph-arrow-u-up-left ph-bold ph-lg"></i>
|
||||
<p v-if="appearNote.repliesCount > 0" :class="$style.footerButtonCount">{{ appearNote.repliesCount }}</p>
|
||||
<p v-if="appearNote.repliesCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.repliesCount) }}</p>
|
||||
</button>
|
||||
<button
|
||||
v-if="canRenote"
|
||||
|
@ -117,7 +117,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
@mousedown="renoted ? undoRenote(appearNote) : boostVisibility()"
|
||||
>
|
||||
<i class="ph-rocket-launch ph-bold ph-lg"></i>
|
||||
<p v-if="appearNote.renoteCount > 0" :class="$style.footerButtonCount">{{ appearNote.renoteCount }}</p>
|
||||
<p v-if="appearNote.renoteCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.renoteCount) }}</p>
|
||||
</button>
|
||||
<button v-else :class="$style.footerButton" class="_button" disabled>
|
||||
<i class="ph-prohibit ph-bold ph-lg"></i>
|
||||
|
@ -135,12 +135,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<button v-if="appearNote.myReaction == null && appearNote.reactionAcceptance !== 'likeOnly'" ref="likeButton" :class="$style.footerButton" class="_button" @click.stop @click="like()">
|
||||
<i class="ph-heart ph-bold ph-lg"></i>
|
||||
</button>
|
||||
<button v-if="appearNote.myReaction == null" ref="reactButton" :class="$style.footerButton" class="_button" @mousedown="react()">
|
||||
<i v-if="appearNote.reactionAcceptance === 'likeOnly'" class="ph-heart ph-bold ph-lg"></i>
|
||||
<button ref="reactButton" :class="$style.footerButton" class="_button" @click="toggleReact()">
|
||||
<i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ph-heart ph-bold ph-lg" style="color: var(--eventReactionHeart);"></i>
|
||||
<i v-else-if="appearNote.myReaction != null" class="ph-minus ph-bold ph-lg" style="color: var(--accent);"></i>
|
||||
<i v-else-if="appearNote.reactionAcceptance === 'likeOnly'" class="ph-heart ph-bold ph-lg"></i>
|
||||
<i v-else class="ph-smiley ph-bold ph-lg"></i>
|
||||
</button>
|
||||
<button v-if="appearNote.myReaction != null" ref="reactButton" :class="$style.footerButton" class="_button" @click.stop @click="undoReact(appearNote)">
|
||||
<i class="ph-minus ph-bold ph-lg"></i>
|
||||
<p v-if="(appearNote.reactionAcceptance === 'likeOnly' || defaultStore.state.showReactionsCount) && appearNote.reactionCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.reactionCount) }}</p>
|
||||
</button>
|
||||
<button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @mousedown="clip()">
|
||||
<i class="ph-paperclip ph-bold ph-lg"></i>
|
||||
|
@ -195,6 +195,7 @@ import { pleaseLogin } from '@/scripts/please-login.js';
|
|||
import { focusPrev, focusNext } from '@/scripts/focus.js';
|
||||
import { checkWordMute } from '@/scripts/check-word-mute.js';
|
||||
import { userPage } from '@/filters/user.js';
|
||||
import number from '@/filters/number.js';
|
||||
import * as os from '@/os.js';
|
||||
import * as sound from '@/scripts/sound.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
|
@ -626,6 +627,14 @@ function undoRenote(note) : void {
|
|||
}
|
||||
}
|
||||
|
||||
function toggleReact() {
|
||||
if (appearNote.value.myReaction == null) {
|
||||
react();
|
||||
} else {
|
||||
undoReact(appearNote.value);
|
||||
}
|
||||
}
|
||||
|
||||
function onContextmenu(ev: MouseEvent): void {
|
||||
if (props.mock) {
|
||||
return;
|
||||
|
|
|
@ -113,10 +113,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkTime :time="appearNote.createdAt" mode="detail" colored/>
|
||||
</MkA>
|
||||
</div>
|
||||
<MkReactionsViewer ref="reactionsViewer" :note="appearNote"/>
|
||||
<MkReactionsViewer v-if="appearNote.reactionAcceptance !== 'likeOnly'" ref="reactionsViewer" :note="appearNote"/>
|
||||
<button class="_button" :class="$style.noteFooterButton" @click="reply()">
|
||||
<i class="ph-arrow-u-up-left ph-bold ph-lg"></i>
|
||||
<p v-if="appearNote.repliesCount > 0" :class="$style.noteFooterButtonCount">{{ appearNote.repliesCount }}</p>
|
||||
<p v-if="appearNote.repliesCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.repliesCount) }}</p>
|
||||
</button>
|
||||
<button
|
||||
v-if="canRenote"
|
||||
|
@ -127,7 +127,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
@mousedown="renoted ? undoRenote() : boostVisibility()"
|
||||
>
|
||||
<i class="ph-rocket-launch ph-bold ph-lg"></i>
|
||||
<p v-if="appearNote.renoteCount > 0" :class="$style.noteFooterButtonCount">{{ appearNote.renoteCount }}</p>
|
||||
<p v-if="appearNote.renoteCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.renoteCount) }}</p>
|
||||
</button>
|
||||
<button v-else class="_button" :class="$style.noteFooterButton" disabled>
|
||||
<i class="ph-prohibit ph-bold ph-lg"></i>
|
||||
|
@ -144,12 +144,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<button v-if="appearNote.myReaction == null && appearNote.reactionAcceptance !== 'likeOnly'" ref="likeButton" :class="$style.noteFooterButton" class="_button" @mousedown="like()">
|
||||
<i class="ph-heart ph-bold ph-lg"></i>
|
||||
</button>
|
||||
<button v-if="appearNote.myReaction == null" ref="reactButton" :class="$style.noteFooterButton" class="_button" @mousedown="react()">
|
||||
<i v-if="appearNote.reactionAcceptance === 'likeOnly'" class="ph-heart ph-bold ph-lg"></i>
|
||||
<button ref="reactButton" :class="$style.noteFooterButton" class="_button" @click="toggleReact()">
|
||||
<i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ph-heart ph-bold ph-lg" style="color: var(--eventReactionHeart);"></i>
|
||||
<i v-else-if="appearNote.myReaction != null" class="ph-minus ph-bold ph-lg" style="color: var(--accent);"></i>
|
||||
<i v-else-if="appearNote.reactionAcceptance === 'likeOnly'" class="ph-heart ph-bold ph-lg"></i>
|
||||
<i v-else class="ph-smiley ph-bold ph-lg"></i>
|
||||
</button>
|
||||
<button v-if="appearNote.myReaction != null" ref="reactButton" class="_button" :class="[$style.noteFooterButton, $style.reacted]" @click="undoReact(appearNote)">
|
||||
<i class="ph-minus ph-bold ph-lg"></i>
|
||||
<p v-if="(appearNote.reactionAcceptance === 'likeOnly' || defaultStore.state.showReactionsCount) && appearNote.reactionCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.reactionCount) }}</p>
|
||||
</button>
|
||||
<button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" class="_button" :class="$style.noteFooterButton" @mousedown="clip()">
|
||||
<i class="ph-paperclip ph-bold ph-lg"></i>
|
||||
|
@ -236,6 +236,7 @@ import { pleaseLogin } from '@/scripts/please-login.js';
|
|||
import { checkWordMute } from '@/scripts/check-word-mute.js';
|
||||
import { userPage } from '@/filters/user.js';
|
||||
import { notePage } from '@/filters/note.js';
|
||||
import number from '@/filters/number.js';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import * as sound from '@/scripts/sound.js';
|
||||
|
@ -594,11 +595,11 @@ function like(): void {
|
|||
}
|
||||
}
|
||||
|
||||
function undoReact(note): void {
|
||||
const oldReaction = note.myReaction;
|
||||
function undoReact(targetNote: Misskey.entities.Note): void {
|
||||
const oldReaction = targetNote.myReaction;
|
||||
if (!oldReaction) return;
|
||||
misskeyApi('notes/reactions/delete', {
|
||||
noteId: note.id,
|
||||
noteId: targetNote.id,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -619,6 +620,14 @@ function undoRenote() : void {
|
|||
}
|
||||
}
|
||||
|
||||
function toggleReact() {
|
||||
if (appearNote.value.myReaction == null) {
|
||||
react();
|
||||
} else {
|
||||
undoReact(appearNote.value);
|
||||
}
|
||||
}
|
||||
|
||||
function onContextmenu(ev: MouseEvent): void {
|
||||
const isLink = (el: HTMLElement): boolean => {
|
||||
if (el.tagName === 'A') return true;
|
||||
|
|
|
@ -8,6 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div :class="$style.head">
|
||||
<MkAvatar v-if="['pollEnded', 'note', 'edited'].includes(notification.type) && notification.note" :class="$style.icon" :user="notification.note.user" link preview/>
|
||||
<MkAvatar v-else-if="['roleAssigned', 'achievementEarned'].includes(notification.type)" :class="$style.icon" :user="$i" link preview/>
|
||||
<div v-else-if="notification.type === 'reaction:grouped' && notification.note.reactionAcceptance === 'likeOnly'" :class="[$style.icon, $style.icon_reactionGroup]"><i class="ph-smiley ph-bold ph-lg" style="line-height: 1;"></i></div>
|
||||
<div v-else-if="notification.type === 'reaction:grouped'" :class="[$style.icon, $style.icon_reactionGroup]"><i class="ph-smiley ph-bold ph-lg" style="line-height: 1;"></i></div>
|
||||
<div v-else-if="notification.type === 'renote:grouped'" :class="[$style.icon, $style.icon_renoteGroup]"><i class="ph-rocket-launch ph-bold ph-lg" style="line-height: 1;"></i></div>
|
||||
<img v-else-if="notification.type === 'test'" :class="$style.icon" :src="infoImageUrl"/>
|
||||
|
@ -60,6 +61,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<span v-else-if="notification.type === 'achievementEarned'">{{ i18n.ts._notification.achievementEarned }}</span>
|
||||
<span v-else-if="notification.type === 'test'">{{ i18n.ts._notification.testNotification }}</span>
|
||||
<MkA v-else-if="notification.type === 'follow' || notification.type === 'mention' || notification.type === 'reply' || notification.type === 'renote' || notification.type === 'quote' || notification.type === 'reaction' || notification.type === 'receiveFollowRequest' || notification.type === 'followRequestAccepted'" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA>
|
||||
<span v-else-if="notification.type === 'reaction:grouped' && notification.note.reactionAcceptance === 'likeOnly'">{{ i18n.tsx._notification.likedBySomeUsers({ n: notification.reactions.length }) }}</span>
|
||||
<span v-else-if="notification.type === 'reaction:grouped'">{{ i18n.tsx._notification.reactedBySomeUsers({ n: notification.reactions.length }) }}</span>
|
||||
<span v-else-if="notification.type === 'renote:grouped'">{{ i18n.tsx._notification.renotedBySomeUsers({ n: notification.users.length }) }}</span>
|
||||
<span v-else-if="notification.type === 'app'">{{ notification.header }}</span>
|
||||
|
@ -211,6 +213,7 @@ const rejectFollowRequest = () => {
|
|||
}
|
||||
|
||||
.icon_reactionGroup,
|
||||
.icon_reactionGroupHeart,
|
||||
.icon_renoteGroup {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
|
@ -223,11 +226,15 @@ const rejectFollowRequest = () => {
|
|||
}
|
||||
|
||||
.icon_reactionGroup {
|
||||
background: #e99a0b;
|
||||
background: var(--eventReaction);
|
||||
}
|
||||
|
||||
.icon_reactionGroupHeart {
|
||||
background: var(--eventReactionHeart);
|
||||
}
|
||||
|
||||
.icon_renoteGroup {
|
||||
background: #36d298;
|
||||
background: var(--eventRenote);
|
||||
}
|
||||
|
||||
.icon_app {
|
||||
|
@ -256,49 +263,49 @@ const rejectFollowRequest = () => {
|
|||
|
||||
.t_follow, .t_followRequestAccepted, .t_receiveFollowRequest {
|
||||
padding: 3px;
|
||||
background: #36aed2;
|
||||
background: var(--eventFollow);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.t_renote {
|
||||
padding: 3px;
|
||||
background: #36d298;
|
||||
background: var(--eventRenote);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.t_quote {
|
||||
padding: 3px;
|
||||
background: #36d298;
|
||||
background: var(--eventRenote);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.t_reply {
|
||||
padding: 3px;
|
||||
background: #007aff;
|
||||
background: var(--eventReply);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.t_mention {
|
||||
padding: 3px;
|
||||
background: #88a6b7;
|
||||
background: var(--eventOther);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.t_pollEnded {
|
||||
padding: 3px;
|
||||
background: #88a6b7;
|
||||
background: var(--eventOther);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.t_achievementEarned {
|
||||
padding: 3px;
|
||||
background: #cb9a11;
|
||||
background: var(--eventAchievement);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.t_roleAssigned {
|
||||
padding: 3px;
|
||||
background: #88a6b7;
|
||||
background: var(--eventOther);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
|
|
@ -63,6 +63,7 @@ const exampleNote = reactive<Misskey.entities.Note>({
|
|||
reactionAcceptance: null,
|
||||
renoteCount: 0,
|
||||
repliesCount: 1,
|
||||
reactionCount: 0,
|
||||
reactions: {},
|
||||
reactionEmojis: {},
|
||||
fileIds: [],
|
||||
|
|
|
@ -68,6 +68,7 @@ const exampleCWNote = reactive<Misskey.entities.Note>({
|
|||
reactionAcceptance: null,
|
||||
renoteCount: 0,
|
||||
repliesCount: 1,
|
||||
reactionCount: 0,
|
||||
reactions: {},
|
||||
reactionEmojis: {},
|
||||
fileIds: [],
|
||||
|
|
|
@ -58,6 +58,7 @@ const exampleNote = reactive<Misskey.entities.Note>({
|
|||
reactionAcceptance: null,
|
||||
renoteCount: 0,
|
||||
repliesCount: 1,
|
||||
reactionCount: 0,
|
||||
reactions: {},
|
||||
reactionEmojis: {},
|
||||
fileIds: ['0000000002'],
|
||||
|
|
|
@ -99,7 +99,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
<MkA v-if="appearNote.channel && !inChannel" :class="$style.channel" :to="`/channels/${appearNote.channel.id}`"><i class="ph-television ph-bold ph-lg"></i> {{ appearNote.channel.name }}</MkA>
|
||||
</div>
|
||||
<MkReactionsViewer :note="appearNote" :maxNumber="16" @click.stop @mockUpdateMyReaction="emitUpdReaction">
|
||||
<MkReactionsViewer v-if="appearNote.reactionAcceptance !== 'likeOnly'" :note="appearNote" :maxNumber="16" @click.stop @mockUpdateMyReaction="emitUpdReaction">
|
||||
<template #more>
|
||||
<div :class="$style.reactionOmitted">{{ i18n.ts.more }}</div>
|
||||
</template>
|
||||
|
@ -107,7 +107,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<footer :class="$style.footer">
|
||||
<button :class="$style.footerButton" class="_button" @click.stop @click="reply()">
|
||||
<i class="ph-arrow-u-up-left ph-bold ph-lg"></i>
|
||||
<p v-if="appearNote.repliesCount > 0" :class="$style.footerButtonCount">{{ appearNote.repliesCount }}</p>
|
||||
<p v-if="appearNote.repliesCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.repliesCount) }}</p>
|
||||
</button>
|
||||
<button
|
||||
v-if="canRenote"
|
||||
|
@ -119,7 +119,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
@mousedown="renoted ? undoRenote(appearNote) : boostVisibility()"
|
||||
>
|
||||
<i class="ph-rocket-launch ph-bold ph-lg"></i>
|
||||
<p v-if="appearNote.renoteCount > 0" :class="$style.footerButtonCount">{{ appearNote.renoteCount }}</p>
|
||||
<p v-if="appearNote.renoteCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.renoteCount) }}</p>
|
||||
</button>
|
||||
<button v-else :class="$style.footerButton" class="_button" disabled>
|
||||
<i class="ph-prohibit ph-bold ph-lg"></i>
|
||||
|
@ -137,12 +137,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<button v-if="appearNote.myReaction == null && appearNote.reactionAcceptance !== 'likeOnly'" ref="likeButton" :class="$style.footerButton" class="_button" @click.stop @click="like()">
|
||||
<i class="ph-heart ph-bold ph-lg"></i>
|
||||
</button>
|
||||
<button v-if="appearNote.myReaction == null" ref="reactButton" :class="$style.footerButton" class="_button" @mousedown="react()">
|
||||
<i v-if="appearNote.reactionAcceptance === 'likeOnly'" class="ph-heart ph-bold ph-lg"></i>
|
||||
<button ref="reactButton" :class="$style.footerButton" class="_button" @click="toggleReact()">
|
||||
<i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ph-heart ph-bold ph-lg" style="color: var(--eventReactionHeart);"></i>
|
||||
<i v-else-if="appearNote.myReaction != null" class="ph-minus ph-bold ph-lg" style="color: var(--accent);"></i>
|
||||
<i v-else-if="appearNote.reactionAcceptance === 'likeOnly'" class="ph-heart ph-bold ph-lg"></i>
|
||||
<i v-else class="ph-smiley ph-bold ph-lg"></i>
|
||||
</button>
|
||||
<button v-if="appearNote.myReaction != null" ref="reactButton" :class="$style.footerButton" class="_button" @click.stop @click="undoReact(appearNote)">
|
||||
<i class="ph-minus ph-bold ph-lg"></i>
|
||||
<p v-if="(appearNote.reactionAcceptance === 'likeOnly' || defaultStore.state.showReactionsCount) && appearNote.reactionCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.reactionCount) }}</p>
|
||||
</button>
|
||||
<button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @mousedown="clip()">
|
||||
<i class="ph-paperclip ph-bold ph-lg"></i>
|
||||
|
@ -196,6 +196,7 @@ import { pleaseLogin } from '@/scripts/please-login.js';
|
|||
import { focusPrev, focusNext } from '@/scripts/focus.js';
|
||||
import { checkWordMute } from '@/scripts/check-word-mute.js';
|
||||
import { userPage } from '@/filters/user.js';
|
||||
import number from '@/filters/number.js';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
import * as sound from '@/scripts/sound.js';
|
||||
|
@ -627,6 +628,14 @@ function undoRenote(note) : void {
|
|||
}
|
||||
}
|
||||
|
||||
function toggleReact() {
|
||||
if (appearNote.value.myReaction == null) {
|
||||
react();
|
||||
} else {
|
||||
undoReact(appearNote.value);
|
||||
}
|
||||
}
|
||||
|
||||
function onContextmenu(ev: MouseEvent): void {
|
||||
if (props.mock) {
|
||||
return;
|
||||
|
|
|
@ -120,11 +120,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkTime :time="appearNote.createdAt" mode="detail" colored/>
|
||||
</MkA>
|
||||
</div>
|
||||
<MkReactionsViewer ref="reactionsViewer" :note="appearNote"/>
|
||||
<MkReactionsViewer v-if="appearNote.reactionAcceptance !== 'likeOnly'" ref="reactionsViewer" :note="appearNote"/>
|
||||
<footer :class="$style.footer">
|
||||
<button class="_button" :class="$style.noteFooterButton" @click="reply()">
|
||||
<i class="ph-arrow-u-up-left ph-bold ph-lg"></i>
|
||||
<p v-if="appearNote.repliesCount > 0" :class="$style.noteFooterButtonCount">{{ appearNote.repliesCount }}</p>
|
||||
<p v-if="appearNote.repliesCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.repliesCount) }}</p>
|
||||
</button>
|
||||
<button
|
||||
v-if="canRenote"
|
||||
|
@ -135,7 +135,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
@mousedown="renoted ? undoRenote() : boostVisibility()"
|
||||
>
|
||||
<i class="ph-rocket-launch ph-bold ph-lg"></i>
|
||||
<p v-if="appearNote.renoteCount > 0" :class="$style.noteFooterButtonCount">{{ appearNote.renoteCount }}</p>
|
||||
<p v-if="appearNote.renoteCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.renoteCount) }}</p>
|
||||
</button>
|
||||
<button v-else class="_button" :class="$style.noteFooterButton" disabled>
|
||||
<i class="ph-prohibit ph-bold ph-lg"></i>
|
||||
|
@ -152,12 +152,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<button v-if="appearNote.myReaction == null && appearNote.reactionAcceptance !== 'likeOnly'" ref="likeButton" :class="$style.noteFooterButton" class="_button" @mousedown="like()">
|
||||
<i class="ph-heart ph-bold ph-lg"></i>
|
||||
</button>
|
||||
<button v-if="appearNote.myReaction == null" ref="reactButton" :class="$style.noteFooterButton" class="_button" @mousedown="react()">
|
||||
<i v-if="appearNote.reactionAcceptance === 'likeOnly'" class="ph-heart ph-bold ph-lg"></i>
|
||||
<button ref="reactButton" :class="$style.footerButton" class="_button" @click="toggleReact()">
|
||||
<i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ph-heart ph-bold ph-lg" style="color: var(--eventReactionHeart);"></i>
|
||||
<i v-else-if="appearNote.myReaction != null" class="ph-minus ph-bold ph-lg" style="color: var(--accent);"></i>
|
||||
<i v-else-if="appearNote.reactionAcceptance === 'likeOnly'" class="ph-heart ph-bold ph-lg"></i>
|
||||
<i v-else class="ph-smiley ph-bold ph-lg"></i>
|
||||
</button>
|
||||
<button v-if="appearNote.myReaction != null" ref="reactButton" class="_button" :class="[$style.noteFooterButton, $style.reacted]" @click="undoReact(appearNote)">
|
||||
<i class="ph-minus ph-bold ph-lg"></i>
|
||||
<p v-if="(appearNote.reactionAcceptance === 'likeOnly' || defaultStore.state.showReactionsCount) && appearNote.reactionCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.reactionCount) }}</p>
|
||||
</button>
|
||||
<button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" class="_button" :class="$style.noteFooterButton" @mousedown="clip()">
|
||||
<i class="ph-paperclip ph-bold ph-lg"></i>
|
||||
|
@ -243,6 +243,7 @@ import SkInstanceTicker from '@/components/SkInstanceTicker.vue';
|
|||
import { pleaseLogin } from '@/scripts/please-login.js';
|
||||
import { checkWordMute } from '@/scripts/check-word-mute.js';
|
||||
import { userPage } from '@/filters/user.js';
|
||||
import number from '@/filters/number.js';
|
||||
import { notePage } from '@/filters/note.js';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
|
@ -603,11 +604,11 @@ function like(): void {
|
|||
}
|
||||
}
|
||||
|
||||
function undoReact(note): void {
|
||||
const oldReaction = note.myReaction;
|
||||
function undoReact(targetNote: Misskey.entities.Note): void {
|
||||
const oldReaction = targetNote.myReaction;
|
||||
if (!oldReaction) return;
|
||||
misskeyApi('notes/reactions/delete', {
|
||||
noteId: note.id,
|
||||
noteId: targetNote.id,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -628,6 +629,14 @@ function undoRenote() : void {
|
|||
}
|
||||
}
|
||||
|
||||
function toggleReact() {
|
||||
if (appearNote.value.myReaction == null) {
|
||||
react();
|
||||
} else {
|
||||
undoReact(appearNote.value);
|
||||
}
|
||||
}
|
||||
|
||||
function onContextmenu(ev: MouseEvent): void {
|
||||
const isLink = (el: HTMLElement): boolean => {
|
||||
if (el.tagName === 'A') return true;
|
||||
|
|
|
@ -14,10 +14,20 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
[$style.form_vertical]: chosen.place === 'vertical',
|
||||
}]"
|
||||
>
|
||||
<a :href="chosen.url" target="_blank" :class="$style.link">
|
||||
<component
|
||||
:is="self ? 'MkA' : 'a'"
|
||||
:class="$style.link"
|
||||
v-bind="self ? {
|
||||
to: chosen.url.substring(local.length),
|
||||
} : {
|
||||
href: chosen.url,
|
||||
rel: 'nofollow noopener',
|
||||
target: '_blank',
|
||||
}"
|
||||
>
|
||||
<img :src="chosen.imageUrl" :class="$style.img">
|
||||
<button class="_button" :class="$style.i" @click.prevent.stop="toggleMenu"><i :class="$style.iIcon" class="ph-info ph-bold ph-lg"></i></button>
|
||||
</a>
|
||||
</component>
|
||||
</div>
|
||||
<div v-else :class="$style.menu">
|
||||
<div :class="$style.menuContainer">
|
||||
|
@ -32,10 +42,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { ref, computed } from 'vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { instance } from '@/instance.js';
|
||||
import { host } from '@/config.js';
|
||||
import { url as local, host } from '@/config.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import * as os from '@/os.js';
|
||||
|
@ -96,6 +106,9 @@ const choseAd = (): Ad | null => {
|
|||
};
|
||||
|
||||
const chosen = ref(choseAd());
|
||||
|
||||
const self = computed(() => chosen.value?.url.startsWith(local));
|
||||
|
||||
const shouldHide = ref(!defaultStore.state.forceShowAds && $i && $i.policies.canHideAds && (props.specify == null));
|
||||
|
||||
function reduceFrequency(): void {
|
||||
|
|
|
@ -373,7 +373,7 @@ export class Router extends EventEmitter<RouterEvent> 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,6 +408,9 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
|
|||
if (cancel) return;
|
||||
}
|
||||
const res = this.navigate(path, null);
|
||||
if (res.route.path === '/:(*)') {
|
||||
location.href = path;
|
||||
} else {
|
||||
this.emit('push', {
|
||||
beforePath,
|
||||
path: res._parsedRoute.fullPath,
|
||||
|
@ -416,6 +419,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
|
|||
key: this.currentKey,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public replace(path: string, key?: string | null) {
|
||||
const res = this.navigate(path, key);
|
||||
|
|
|
@ -60,6 +60,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkSwitch v-model="advancedMfm">{{ i18n.ts.enableAdvancedMfm }}</MkSwitch>
|
||||
<MkSwitch v-if="advancedMfm" v-model="animatedMfm">{{ i18n.ts.enableAnimatedMfm }}</MkSwitch>
|
||||
<MkSwitch v-if="advancedMfm" v-model="enableQuickAddMfmFunction">{{ i18n.ts.enableQuickAddMfmFunction }}</MkSwitch>
|
||||
<MkSwitch v-model="showReactionsCount">{{ i18n.ts.showReactionsCount }}</MkSwitch>
|
||||
<MkSwitch v-model="showGapBetweenNotesInTimeline">{{ i18n.ts.showGapBetweenNotesInTimeline }}</MkSwitch>
|
||||
<MkSwitch v-model="loadRawImages">{{ i18n.ts.loadRawImages }}</MkSwitch>
|
||||
<MkSwitch v-model="showTickerOnReplies">Show instance ticker on replies</MkSwitch>
|
||||
|
@ -328,6 +329,7 @@ const useBlurEffect = computed(defaultStore.makeGetterSetter('useBlurEffect'));
|
|||
const showGapBetweenNotesInTimeline = computed(defaultStore.makeGetterSetter('showGapBetweenNotesInTimeline'));
|
||||
const animatedMfm = computed(defaultStore.makeGetterSetter('animatedMfm'));
|
||||
const advancedMfm = computed(defaultStore.makeGetterSetter('advancedMfm'));
|
||||
const showReactionsCount = computed(defaultStore.makeGetterSetter('showReactionsCount'));
|
||||
const enableQuickAddMfmFunction = computed(defaultStore.makeGetterSetter('enableQuickAddMfmFunction'));
|
||||
const emojiStyle = computed(defaultStore.makeGetterSetter('emojiStyle'));
|
||||
const disableDrawer = computed(defaultStore.makeGetterSetter('disableDrawer'));
|
||||
|
|
|
@ -70,6 +70,7 @@ const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [
|
|||
'animation',
|
||||
'animatedMfm',
|
||||
'advancedMfm',
|
||||
'showReactionsCount',
|
||||
'loadRawImages',
|
||||
'warnMissingAltText',
|
||||
'imageNewTab',
|
||||
|
|
|
@ -54,6 +54,7 @@ export function useNoteCapture(props: {
|
|||
const currentCount = (note.value.reactions || {})[reaction] || 0;
|
||||
|
||||
note.value.reactions[reaction] = currentCount + 1;
|
||||
note.value.reactionCount += 1;
|
||||
|
||||
if ($i && (body.userId === $i.id)) {
|
||||
note.value.myReaction = reaction;
|
||||
|
@ -68,6 +69,7 @@ export function useNoteCapture(props: {
|
|||
const currentCount = (note.value.reactions || {})[reaction] || 0;
|
||||
|
||||
note.value.reactions[reaction] = Math.max(0, currentCount - 1);
|
||||
note.value.reactionCount = Math.max(0, note.value.reactionCount - 1);
|
||||
if (note.value.reactions[reaction] === 0) delete note.value.reactions[reaction];
|
||||
|
||||
if ($i && (body.userId === $i.id)) {
|
||||
|
|
|
@ -256,6 +256,10 @@ export const defaultStore = markRaw(new Storage('base', {
|
|||
where: 'device',
|
||||
default: true,
|
||||
},
|
||||
showReactionsCount: {
|
||||
where: 'device',
|
||||
default: false,
|
||||
},
|
||||
enableQuickAddMfmFunction: {
|
||||
where: 'device',
|
||||
default: false,
|
||||
|
|
|
@ -41,6 +41,13 @@
|
|||
--thread-width: 2px;
|
||||
|
||||
//--ad: rgb(255 169 0 / 10%);
|
||||
--eventFollow: #36aed2;
|
||||
--eventRenote: #36d298;
|
||||
--eventReply: #007aff;
|
||||
--eventReactionHeart: #dd2e44;
|
||||
--eventReaction: #e99a0b;
|
||||
--eventAchievement: #cb9a11;
|
||||
--eventOther: #88a6b7;
|
||||
}
|
||||
|
||||
html.radius-misskey {
|
||||
|
|
|
@ -49,6 +49,9 @@ const devConfig = {
|
|||
},
|
||||
'/url': httpUrl,
|
||||
'/proxy': httpUrl,
|
||||
'/_info_card_': httpUrl,
|
||||
'/bios': httpUrl,
|
||||
'/cli': httpUrl,
|
||||
},
|
||||
},
|
||||
build: {
|
||||
|
|
|
@ -4119,6 +4119,7 @@ export type components = {
|
|||
reactions: {
|
||||
[key: string]: number;
|
||||
};
|
||||
reactionCount: number;
|
||||
renoteCount: number;
|
||||
repliesCount: number;
|
||||
uri?: string;
|
||||
|
|
1111
pnpm-lock.yaml
1111
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue