kinds).includes(s));
if (!scopes.length) {
throw new AuthorizationError('`scope` parameter has no known scope', 'invalid_scope');
}
@@ -448,30 +448,24 @@ export class OAuth2ProviderService {
return [null, clientInfo, redirectURI];
})().then(args => done(...args), err => done(err));
}) as ValidateFunctionArity2));
- fastify.use('/oauth/authorize', this.#server.errorHandler({
+ fastify.use('/authorize', this.#server.errorHandler({
mode: 'indirect',
modes: getQueryMode(this.config.url),
}));
- fastify.use('/oauth/authorize', this.#server.errorHandler());
+ fastify.use('/authorize', this.#server.errorHandler());
- fastify.use('/oauth/decision', bodyParser.urlencoded({ extended: false }));
- fastify.use('/oauth/decision', this.#server.decision((req, done) => {
+ fastify.use('/decision', bodyParser.urlencoded({ extended: false }));
+ fastify.use('/decision', this.#server.decision((req, done) => {
const { body } = req as OAuth2DecisionRequest;
this.#logger.info(`Received the decision. Cancel: ${!!body.cancel}`);
req.user = body.login_token;
done(null, undefined);
}));
- fastify.use('/oauth/decision', this.#server.errorHandler());
-
- // Clients may use JSON or urlencoded
- fastify.use('/oauth/token', bodyParser.urlencoded({ extended: false }));
- fastify.use('/oauth/token', bodyParser.json({ strict: true }));
- fastify.use('/oauth/token', this.#server.token());
- fastify.use('/oauth/token', this.#server.errorHandler());
+ fastify.use('/decision', this.#server.errorHandler());
// Return 404 for any unknown paths under /oauth so that clients can know
// whether a certain endpoint is supported or not.
- fastify.all('/oauth/*', async (_request, reply) => {
+ fastify.all('/*', async (_request, reply) => {
reply.code(404);
reply.send({
error: {
@@ -483,4 +477,17 @@ export class OAuth2ProviderService {
});
});
}
+
+ @bindThis
+ public async createTokenServer(fastify: FastifyInstance): Promise {
+ fastify.register(fastifyCors);
+ fastify.post('', async () => { });
+
+ await fastify.register(fastifyExpress);
+ // Clients may use JSON or urlencoded
+ fastify.use('', bodyParser.urlencoded({ extended: false }));
+ fastify.use('', bodyParser.json({ strict: true }));
+ fastify.use('', this.#server.token());
+ fastify.use('', this.#server.errorHandler());
+ }
}
diff --git a/packages/backend/src/server/web/FeedService.ts b/packages/backend/src/server/web/FeedService.ts
index 3bdf9d71e8..cdb67bb7e7 100644
--- a/packages/backend/src/server/web/FeedService.ts
+++ b/packages/backend/src/server/web/FeedService.ts
@@ -60,7 +60,7 @@ export class FeedService {
title: `${author.name} (@${user.username}@${this.config.host})`,
updated: notes.length !== 0 ? this.idService.parse(notes[0].id).date : undefined,
generator: 'CherryPick',
- description: `${user.notesCount} Notes, ${profile.ffVisibility === 'public' ? user.followingCount : '?'} Following, ${profile.ffVisibility === 'public' ? user.followersCount : '?'} Followers${profile.description ? ` · ${profile.description}` : ''}`,
+ description: `${user.notesCount} Notes, ${profile.followingVisibility === 'public' ? user.followingCount : '?'} Following, ${profile.followersVisibility === 'public' ? user.followersCount : '?'} Followers${profile.description ? ` · ${profile.description}` : ''}`,
link: author.link,
image: user.avatarUrl ?? this.userEntityService.getIdenticonUrl(user),
feedLinks: {
diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts
index f2c2aeff99..c5343c810a 100644
--- a/packages/backend/src/server/web/UrlPreviewService.ts
+++ b/packages/backend/src/server/web/UrlPreviewService.ts
@@ -4,7 +4,7 @@
*/
import { Inject, Injectable } from '@nestjs/common';
-import { summaly } from 'summaly';
+import { summaly } from '@misskey-dev/summaly';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { MetaService } from '@/core/MetaService.js';
diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js
index 91e9101123..ef8845c56a 100644
--- a/packages/backend/src/server/web/boot.js
+++ b/packages/backend/src/server/web/boot.js
@@ -183,6 +183,7 @@
Clear the browser cache / ブラウザのキャッシュをクリアする / 브라우저의 캐시 지우기
Update your os and browser / ブラウザおよびOSを最新バージョンに更新する / 브라우저와 OS를 최신 버전으로 업데이트 하기
Disable an adblocker / アドブロッカーを無効にする / 광고 차단기를 비활성화 하기
+ (Tor Browser) Set dom.webaudio.enabled to true / dom.webaudio.enabledをtrueに設定する / dom.webaudio.enabled를 true로 설정하기
Other options / その他のオプション / 기타 옵션
diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug
index fd80619675..1423d39b8a 100644
--- a/packages/backend/src/server/web/views/base.pug
+++ b/packages/backend/src/server/web/views/base.pug
@@ -36,7 +36,7 @@ html
link(rel='prefetch' href=infoImageUrl)
link(rel='prefetch' href=notFoundImageUrl)
//- https://github.com/misskey-dev/misskey/issues/9842
- link(rel='stylesheet' href='/assets/tabler-icons/tabler-icons.min.css?v2.37.0')
+ link(rel='stylesheet' href=`/assets/tabler-icons.${version}/tabler-icons.min.css`)
link(rel='modulepreload' href=`/vite/${clientEntry.file}`)
if !config.clientManifestExists
diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts
index eb262e2f25..6e0fe4e5f9 100644
--- a/packages/backend/src/types.ts
+++ b/packages/backend/src/types.ts
@@ -14,19 +14,36 @@
* pollEnded - 自分のアンケートもしくは自分が投票したアンケートが終了した
* receiveFollowRequest - フォローリクエストされた
* followRequestAccepted - 自分の送ったフォローリクエストが承認された
+ * roleAssigned - ロールが付与された
* groupInvited - グループに招待された
* achievementEarned - 実績を獲得
* app - アプリ通知
* test - テスト通知(サーバー側)
*/
-export const notificationTypes = ['note', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'achievementEarned', 'app', 'test'] as const;
+export const notificationTypes = [
+ 'note',
+ 'follow',
+ 'mention',
+ 'reply',
+ 'renote',
+ 'quote',
+ 'reaction',
+ 'pollEnded',
+ 'receiveFollowRequest',
+ 'followRequestAccepted',
+ 'roleAssigned',
+ 'groupInvited',
+ 'achievementEarned',
+ 'app',
+ 'test'] as const;
export const obsoleteNotificationTypes = ['pollVote'/*, 'groupInvited'*/] as const;
export const noteVisibilities = ['public', 'home', 'followers', 'specified'] as const;
export const mutedNoteReasons = ['word', 'manual', 'spam', 'other'] as const;
-export const ffVisibility = ['public', 'followers', 'private'] as const;
+export const followingVisibilities = ['public', 'followers', 'private'] as const;
+export const followersVisibilities = ['public', 'followers', 'private'] as const;
export const moderationLogTypes = [
'updateServerSettings',
diff --git a/packages/backend/test-server/.eslintrc.cjs b/packages/backend/test-server/.eslintrc.cjs
new file mode 100644
index 0000000000..c261741a36
--- /dev/null
+++ b/packages/backend/test-server/.eslintrc.cjs
@@ -0,0 +1,32 @@
+module.exports = {
+ parserOptions: {
+ tsconfigRootDir: __dirname,
+ project: ['./tsconfig.json'],
+ },
+ extends: [
+ '../../shared/.eslintrc.js',
+ ],
+ rules: {
+ 'import/order': ['warn', {
+ 'groups': ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
+ 'pathGroups': [
+ {
+ 'pattern': '@/**',
+ 'group': 'external',
+ 'position': 'after'
+ }
+ ],
+ }],
+ 'no-restricted-globals': [
+ 'error',
+ {
+ 'name': '__dirname',
+ 'message': 'Not in ESModule. Use `import.meta.url` instead.'
+ },
+ {
+ 'name': '__filename',
+ 'message': 'Not in ESModule. Use `import.meta.url` instead.'
+ }
+ ]
+ },
+};
diff --git a/packages/backend/test-server/.swcrc b/packages/backend/test-server/.swcrc
new file mode 100644
index 0000000000..e3d6935169
--- /dev/null
+++ b/packages/backend/test-server/.swcrc
@@ -0,0 +1,23 @@
+{
+ "$schema": "https://json.schemastore.org/swcrc",
+ "jsc": {
+ "parser": {
+ "syntax": "typescript",
+ "dynamicImport": true,
+ "decorators": true
+ },
+ "transform": {
+ "legacyDecorator": true,
+ "decoratorMetadata": true
+ },
+ "experimental": {
+ "keepImportAssertions": true
+ },
+ "baseUrl": "../built",
+ "paths": {
+ "@/*": ["*"]
+ },
+ "target": "es2022"
+ },
+ "minify": false
+}
diff --git a/packages/backend/test-server/entry.ts b/packages/backend/test-server/entry.ts
new file mode 100644
index 0000000000..866a7e1f5b
--- /dev/null
+++ b/packages/backend/test-server/entry.ts
@@ -0,0 +1,80 @@
+import { portToPid } from 'pid-port';
+import fkill from 'fkill';
+import Fastify from 'fastify';
+import { NestFactory } from '@nestjs/core';
+import { MainModule } from '@/MainModule.js';
+import { ServerService } from '@/server/ServerService.js';
+import { loadConfig } from '@/config.js';
+import { NestLogger } from '@/NestLogger.js';
+
+const config = loadConfig();
+const originEnv = JSON.stringify(process.env);
+
+process.env.NODE_ENV = 'test';
+
+/**
+ * テスト用のサーバインスタンスを起動する
+ */
+async function launch() {
+ await killTestServer();
+
+ console.log('starting application...');
+
+ const app = await NestFactory.createApplicationContext(MainModule, {
+ logger: new NestLogger(),
+ });
+ const serverService = app.get(ServerService);
+ await serverService.launch();
+
+ await startControllerEndpoints();
+
+ // ジョブキューは必要な時にテストコード側で起動する
+ // ジョブキューが動くとテスト結果の確認に支障が出ることがあるので意図的に動かさないでいる
+
+ console.log('application initialized.');
+}
+
+/**
+ * 既に重複したポートで待ち受けしているサーバがある場合はkillする
+ */
+async function killTestServer() {
+ //
+ try {
+ const pid = await portToPid(config.port);
+ if (pid) {
+ await fkill(pid, { force: true });
+ }
+ } catch {
+ // NOP;
+ }
+}
+
+/**
+ * 別プロセスに切り離してしまったが故に出来なくなった環境変数の書き換え等を実現するためのエンドポイントを作る
+ * @param port
+ */
+async function startControllerEndpoints(port = config.port + 1000) {
+ const fastify = Fastify();
+
+ fastify.post<{ Body: { key?: string, value?: string } }>('/env', async (req, res) => {
+ console.log(req.body);
+ const key = req.body['key'];
+ if (!key) {
+ res.code(400).send({ success: false });
+ return;
+ }
+
+ process.env[key] = req.body['value'];
+
+ res.code(200).send({ success: true });
+ });
+
+ fastify.post<{ Body: { key?: string, value?: string } }>('/env-reset', async (req, res) => {
+ process.env = JSON.parse(originEnv);
+ res.code(200).send({ success: true });
+ });
+
+ await fastify.listen({ port: port, host: 'localhost' });
+}
+
+export default launch;
diff --git a/packages/backend/test-server/tsconfig.json b/packages/backend/test-server/tsconfig.json
new file mode 100644
index 0000000000..10313699c2
--- /dev/null
+++ b/packages/backend/test-server/tsconfig.json
@@ -0,0 +1,52 @@
+{
+ "compilerOptions": {
+ "allowJs": true,
+ "noEmitOnError": true,
+ "noImplicitAny": true,
+ "noImplicitReturns": true,
+ "noUnusedParameters": false,
+ "noUnusedLocals": false,
+ "noFallthroughCasesInSwitch": true,
+ "declaration": false,
+ "sourceMap": true,
+ "target": "ES2022",
+ "module": "nodenext",
+ "moduleResolution": "nodenext",
+ "allowSyntheticDefaultImports": true,
+ "removeComments": false,
+ "noLib": false,
+ "strict": true,
+ "strictNullChecks": true,
+ "strictPropertyInitialization": false,
+ "skipLibCheck": true,
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "rootDir": "../src",
+ "baseUrl": "./",
+ "paths": {
+ "@/*": ["../src/*"]
+ },
+ "outDir": "../built-test",
+ "types": [
+ "node"
+ ],
+ "typeRoots": [
+ "../src/@types",
+ "../node_modules/@types",
+ "../node_modules"
+ ],
+ "lib": [
+ "esnext"
+ ]
+ },
+ "compileOnSave": false,
+ "include": [
+ "./**/*.ts",
+ "../src/**/*.ts"
+ ],
+ "exclude": [
+ "../src/**/*.test.ts"
+ ]
+}
diff --git a/packages/backend/test/docker-compose.yml b/packages/backend/test/docker-compose.yml
index 2e97e4d179..f51f88ccde 100644
--- a/packages/backend/test/docker-compose.yml
+++ b/packages/backend/test/docker-compose.yml
@@ -7,7 +7,7 @@ services:
- "127.0.0.1:56312:6379"
dbtest:
- image: postgres:13
+ image: postgres:15
ports:
- "127.0.0.1:54312:5432"
environment:
diff --git a/packages/backend/test/e2e/2fa.ts b/packages/backend/test/e2e/2fa.ts
index eee6757495..e7e89002d0 100644
--- a/packages/backend/test/e2e/2fa.ts
+++ b/packages/backend/test/e2e/2fa.ts
@@ -10,7 +10,7 @@ import * as crypto from 'node:crypto';
import cbor from 'cbor';
import * as OTPAuth from 'otpauth';
import { loadConfig } from '@/config.js';
-import { api, signup, startServer } from '../utils.js';
+import { api, signup } from '../utils.js';
import type {
AuthenticationResponseJSON,
AuthenticatorAssertionResponseJSON,
@@ -19,12 +19,10 @@ import type {
PublicKeyCredentialRequestOptionsJSON,
RegistrationResponseJSON,
} from '@simplewebauthn/typescript-types';
-import type { INestApplicationContext } from '@nestjs/common';
import type * as misskey from 'cherrypick-js';
describe('2要素認証', () => {
- let app: INestApplicationContext;
- let alice: misskey.entities.MeSignup;
+ let alice: misskey.entities.SignupResponse;
const config = loadConfig();
const password = 'test';
@@ -185,14 +183,9 @@ describe('2要素認証', () => {
};
beforeAll(async () => {
- app = await startServer();
alice = await signup({ username, password });
}, 1000 * 60 * 2);
- afterAll(async () => {
- await app.close();
- });
-
test('が設定でき、OTPでログインできる。', async () => {
const registerResponse = await api('/i/2fa/register', {
password,
diff --git a/packages/backend/test/e2e/antennas.ts b/packages/backend/test/e2e/antennas.ts
index c94dad7a2c..1fb35574ad 100644
--- a/packages/backend/test/e2e/antennas.ts
+++ b/packages/backend/test/e2e/antennas.ts
@@ -6,24 +6,20 @@
process.env.NODE_ENV = 'test';
import * as assert from 'assert';
-import { inspect } from 'node:util';
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
import type { Packed } from '@/misc/json-schema.js';
import {
- signup,
+ api,
+ failedApiCall,
post,
- userList,
- page,
role,
- startServer,
- api,
+ signup,
successfulApiCall,
- failedApiCall,
- uploadFile,
testPaginationConsistency,
+ uploadFile,
+ userList,
} from '../utils.js';
import type * as misskey from 'cherrypick-js';
-import type { INestApplicationContext } from '@nestjs/common';
const compareBy = (selector: (s: T) => string = (s: T): string => s.id) => (a: T, b: T): number => {
return selector(a).localeCompare(selector(b));
@@ -37,7 +33,7 @@ describe('アンテナ', () => {
// - srcのenumにgroupが残っている
// - userGroupIdが残っている, isActiveがない
type Antenna = misskey.entities.Antenna | Packed<'Antenna'>;
- type User = misskey.entities.MeSignup;
+ type User = misskey.entities.SignupResponse;
type Note = misskey.entities.Note;
// アンテナを作成できる最小のパラメタ
@@ -55,8 +51,6 @@ describe('アンテナ', () => {
withReplies: false,
};
- let app: INestApplicationContext;
-
let root: User;
let alice: User;
let bob: User;
@@ -80,10 +74,6 @@ describe('アンテナ', () => {
let userMutingAlice: User;
let userMutedByAlice: User;
- beforeAll(async () => {
- app = await startServer();
- }, 1000 * 60 * 2);
-
beforeAll(async () => {
root = await signup({ username: 'root' });
alice = await signup({ username: 'alice' });
@@ -137,10 +127,6 @@ describe('アンテナ', () => {
await api('mute/create', { userId: userMutedByAlice.id }, alice);
}, 1000 * 60 * 10);
- afterAll(async () => {
- await app.close();
- });
-
beforeEach(async () => {
// テスト間で影響し合わないように毎回全部消す。
for (const user of [alice, bob]) {
diff --git a/packages/backend/test/e2e/api-visibility.ts b/packages/backend/test/e2e/api-visibility.ts
index 658cbfc4f9..9654ecf35c 100644
--- a/packages/backend/test/e2e/api-visibility.ts
+++ b/packages/backend/test/e2e/api-visibility.ts
@@ -6,33 +6,22 @@
process.env.NODE_ENV = 'test';
import * as assert from 'assert';
-import { signup, api, post, startServer } from '../utils.js';
-import type { INestApplicationContext } from '@nestjs/common';
+import { api, post, signup } from '../utils.js';
import type * as misskey from 'cherrypick-js';
describe('API visibility', () => {
- let app: INestApplicationContext;
-
- beforeAll(async () => {
- app = await startServer();
- }, 1000 * 60 * 2);
-
- afterAll(async () => {
- await app.close();
- });
-
describe('Note visibility', () => {
//#region vars
/** ヒロイン */
- let alice: misskey.entities.MeSignup;
+ let alice: misskey.entities.SignupResponse;
/** フォロワー */
- let follower: misskey.entities.MeSignup;
+ let follower: misskey.entities.SignupResponse;
/** 非フォロワー */
- let other: misskey.entities.MeSignup;
+ let other: misskey.entities.SignupResponse;
/** 非フォロワーでもリプライやメンションをされた人 */
- let target: misskey.entities.MeSignup;
+ let target: misskey.entities.SignupResponse;
/** specified mentionでmentionを飛ばされる人 */
- let target2: misskey.entities.MeSignup;
+ let target2: misskey.entities.SignupResponse;
/** public-post */
let pub: any;
diff --git a/packages/backend/test/e2e/api.ts b/packages/backend/test/e2e/api.ts
index 3c19954221..576dd87454 100644
--- a/packages/backend/test/e2e/api.ts
+++ b/packages/backend/test/e2e/api.ts
@@ -7,27 +7,30 @@ process.env.NODE_ENV = 'test';
import * as assert from 'assert';
import { IncomingMessage } from 'http';
-import { signup, api, startServer, successfulApiCall, failedApiCall, uploadFile, waitFire, connectStream, relativeFetch } from '../utils.js';
-import type { INestApplicationContext } from '@nestjs/common';
+import {
+ api,
+ connectStream,
+ createAppToken,
+ failedApiCall,
+ relativeFetch,
+ signup,
+ successfulApiCall,
+ uploadFile,
+ waitFire,
+} from '../utils.js';
import type * as misskey from 'cherrypick-js';
describe('API', () => {
- let app: INestApplicationContext;
- let alice: misskey.entities.MeSignup;
- let bob: misskey.entities.MeSignup;
- let carol: misskey.entities.MeSignup;
+ let alice: misskey.entities.SignupResponse;
+ let bob: misskey.entities.SignupResponse;
+ let carol: misskey.entities.SignupResponse;
beforeAll(async () => {
- app = await startServer();
alice = await signup({ username: 'alice' });
bob = await signup({ username: 'bob' });
carol = await signup({ username: 'carol' });
}, 1000 * 60 * 2);
- afterAll(async () => {
- await app.close();
- });
-
describe('General validation', () => {
test('wrong type', async () => {
const res = await api('/test', {
@@ -89,6 +92,11 @@ describe('API', () => {
});
test('管理者専用のAPIのアクセス制限', async () => {
+ const application = await createAppToken(alice, ['read:account']);
+ const application2 = await createAppToken(alice, ['read:admin:index-stats']);
+ const application3 = await createAppToken(bob, []);
+ const application4 = await createAppToken(bob, ['read:admin:index-stats']);
+
// aliceは管理者、APIを使える
await successfulApiCall({
endpoint: '/admin/get-index-stats',
@@ -128,6 +136,42 @@ describe('API', () => {
code: 'AUTHENTICATION_FAILED',
id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14',
});
+
+ await successfulApiCall({
+ endpoint: '/admin/get-index-stats',
+ parameters: {},
+ user: { token: application2 },
+ });
+
+ await failedApiCall({
+ endpoint: '/admin/get-index-stats',
+ parameters: {},
+ user: { token: application },
+ }, {
+ status: 403,
+ code: 'PERMISSION_DENIED',
+ id: '1370e5b7-d4eb-4566-bb1d-7748ee6a1838',
+ });
+
+ await failedApiCall({
+ endpoint: '/admin/get-index-stats',
+ parameters: {},
+ user: { token: application3 },
+ }, {
+ status: 403,
+ code: 'ROLE_PERMISSION_DENIED',
+ id: 'c3d38592-54c0-429d-be96-5636b0431a61',
+ });
+
+ await failedApiCall({
+ endpoint: '/admin/get-index-stats',
+ parameters: {},
+ user: { token: application4 },
+ }, {
+ status: 403,
+ code: 'ROLE_PERMISSION_DENIED',
+ id: 'c3d38592-54c0-429d-be96-5636b0431a61',
+ });
});
describe('Authentication header', () => {
diff --git a/packages/backend/test/e2e/block.ts b/packages/backend/test/e2e/block.ts
index 3505b5572a..006a3481a1 100644
--- a/packages/backend/test/e2e/block.ts
+++ b/packages/backend/test/e2e/block.ts
@@ -6,29 +6,21 @@
process.env.NODE_ENV = 'test';
import * as assert from 'assert';
-import { signup, api, post, startServer } from '../utils.js';
-import type { INestApplicationContext } from '@nestjs/common';
+import { api, post, signup } from '../utils.js';
import type * as misskey from 'cherrypick-js';
describe('Block', () => {
- let app: INestApplicationContext;
-
// alice blocks bob
- let alice: misskey.entities.MeSignup;
- let bob: misskey.entities.MeSignup;
- let carol: misskey.entities.MeSignup;
+ let alice: misskey.entities.SignupResponse;
+ let bob: misskey.entities.SignupResponse;
+ let carol: misskey.entities.SignupResponse;
beforeAll(async () => {
- app = await startServer();
alice = await signup({ username: 'alice' });
bob = await signup({ username: 'bob' });
carol = await signup({ username: 'carol' });
}, 1000 * 60 * 2);
- afterAll(async () => {
- await app.close();
- });
-
test('Block作成', async () => {
const res = await api('/blocking/create', {
userId: bob.id,
diff --git a/packages/backend/test/e2e/clips.ts b/packages/backend/test/e2e/clips.ts
index 25ec521d2c..231d426e38 100644
--- a/packages/backend/test/e2e/clips.ts
+++ b/packages/backend/test/e2e/clips.ts
@@ -18,25 +18,13 @@ import { paramDef as UnfavoriteParamDef } from '@/server/api/endpoints/clips/unf
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 {
- signup,
- post,
- startServer,
- api,
- successfulApiCall,
- failedApiCall,
- ApiRequest,
- hiddenNote,
-} from '../utils.js';
-import type { INestApplicationContext } from '@nestjs/common';
+import { api, ApiRequest, failedApiCall, hiddenNote, post, signup, successfulApiCall } from '../utils.js';
describe('クリップ', () => {
type User = Packed<'User'>;
type Note = Packed<'Note'>;
type Clip = Packed<'Clip'>;
- let app: INestApplicationContext;
-
let alice: User;
let bob: User;
let aliceNote: Note;
@@ -145,7 +133,6 @@ describe('クリップ', () => {
};
beforeAll(async () => {
- app = await startServer();
alice = await signup({ username: 'alice' });
bob = await signup({ username: 'bob' });
@@ -160,10 +147,6 @@ describe('クリップ', () => {
bobSpecifiedNote = await post(bob, { text: 'specified only', visibility: 'specified' }) as any;
}, 1000 * 60 * 2);
- afterAll(async () => {
- await app.close();
- });
-
afterEach(async () => {
// テスト間で影響し合わないように毎回全部消す。
for (const user of [alice, bob]) {
diff --git a/packages/backend/test/e2e/endpoints.ts b/packages/backend/test/e2e/endpoints.ts
index fb6b442516..7482eae9ae 100644
--- a/packages/backend/test/e2e/endpoints.ts
+++ b/packages/backend/test/e2e/endpoints.ts
@@ -10,30 +10,22 @@ import * as assert from 'assert';
// https://github.com/node-fetch/node-fetch/pull/1664
import { Blob } from 'node-fetch';
import { MiUser } from '@/models/_.js';
-import { startServer, signup, post, api, uploadFile, simpleGet, initTestDb } from '../utils.js';
-import type { INestApplicationContext } from '@nestjs/common';
+import { api, initTestDb, post, signup, simpleGet, uploadFile } from '../utils.js';
import type * as misskey from 'cherrypick-js';
describe('Endpoints', () => {
- let app: INestApplicationContext;
-
- let alice: misskey.entities.MeSignup;
- let bob: misskey.entities.MeSignup;
- let carol: misskey.entities.MeSignup;
- let dave: misskey.entities.MeSignup;
+ let alice: misskey.entities.SignupResponse;
+ let bob: misskey.entities.SignupResponse;
+ let carol: misskey.entities.SignupResponse;
+ let dave: misskey.entities.SignupResponse;
beforeAll(async () => {
- app = await startServer();
alice = await signup({ username: 'alice' });
bob = await signup({ username: 'bob' });
carol = await signup({ username: 'carol' });
dave = await signup({ username: 'dave' });
}, 1000 * 60 * 2);
- afterAll(async () => {
- await app.close();
- });
-
describe('signup', () => {
test('不正なユーザー名でアカウントが作成できない', async () => {
const res = await api('signup', {
@@ -710,6 +702,18 @@ describe('Endpoints', () => {
assert.strictEqual(res.status, 400);
});
+ test('不正なファイル名で怒られる', async () => {
+ const file = (await uploadFile(alice)).body;
+ const newName = '';
+
+ const res = await api('/drive/files/update', {
+ fileId: file.id,
+ name: newName,
+ }, alice);
+
+ assert.strictEqual(res.status, 400);
+ });
+
test('間違ったIDで怒られる', async () => {
const res = await api('/drive/files/update', {
fileId: 'kyoppie',
diff --git a/packages/backend/test/e2e/exports.ts b/packages/backend/test/e2e/exports.ts
new file mode 100644
index 0000000000..8040f71658
--- /dev/null
+++ b/packages/backend/test/e2e/exports.ts
@@ -0,0 +1,193 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey, cherrypick contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+process.env.NODE_ENV = 'test';
+
+import * as assert from 'assert';
+import { api, port, post, signup, startJobQueue } from '../utils.js';
+import type { INestApplicationContext } from '@nestjs/common';
+import type * as misskey from 'cherrypick-js';
+
+describe('export-clips', () => {
+ let queue: INestApplicationContext;
+ let alice: misskey.entities.SignupResponse;
+ let bob: misskey.entities.SignupResponse;
+
+ // XXX: Any better way to get the result?
+ async function pollFirstDriveFile() {
+ while (true) {
+ const files = (await api('/drive/files', {}, alice)).body;
+ if (!files.length) {
+ await new Promise(r => setTimeout(r, 100));
+ continue;
+ }
+ if (files.length > 1) {
+ throw new Error('Too many files?');
+ }
+ 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();
+ }
+ }
+
+ beforeAll(async () => {
+ queue = await startJobQueue();
+ alice = await signup({ username: 'alice' });
+ bob = await signup({ username: 'bob' });
+ }, 1000 * 60 * 2);
+
+ afterAll(async () => {
+ await queue.close();
+ });
+
+ beforeEach(async () => {
+ // Clean all clips and files of alice
+ const clips = (await api('/clips/list', {}, alice)).body;
+ for (const clip of clips) {
+ 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;
+ for (const file of files) {
+ const res = await api('/drive/files/delete', { fileId: file.id }, alice);
+ if (res.status !== 204) {
+ throw new Error('Failed to delete file');
+ }
+ }
+ });
+
+ test('basic export', async () => {
+ let res = await api('/clips/create', {
+ name: 'foo',
+ description: 'bar',
+ }, alice);
+ assert.strictEqual(res.status, 200);
+
+ res = await api('/i/export-clips', {}, alice);
+ assert.strictEqual(res.status, 204);
+
+ const exported = await pollFirstDriveFile();
+ assert.strictEqual(exported[0].name, 'foo');
+ assert.strictEqual(exported[0].description, 'bar');
+ assert.strictEqual(exported[0].clipNotes.length, 0);
+ });
+
+ test('export with notes', async () => {
+ let res = await api('/clips/create', {
+ name: 'foo',
+ description: 'bar',
+ }, alice);
+ assert.strictEqual(res.status, 200);
+ const clip = res.body;
+
+ const note1 = await post(alice, {
+ text: 'baz1',
+ });
+
+ const note2 = await post(alice, {
+ text: 'baz2',
+ poll: {
+ choices: ['sakura', 'izumi', 'ako'],
+ },
+ });
+
+ for (const note of [note1, note2]) {
+ 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);
+ assert.strictEqual(res.status, 204);
+
+ const exported = await pollFirstDriveFile();
+ assert.strictEqual(exported[0].name, 'foo');
+ assert.strictEqual(exported[0].description, 'bar');
+ assert.strictEqual(exported[0].clipNotes.length, 2);
+ assert.strictEqual(exported[0].clipNotes[0].note.text, 'baz1');
+ assert.strictEqual(exported[0].clipNotes[1].note.text, 'baz2');
+ assert.deepStrictEqual(exported[0].clipNotes[1].note.poll.choices[0], 'sakura');
+ });
+
+ test('multiple clips', async () => {
+ 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', {
+ name: 'yuri',
+ description: 'yuri',
+ }, alice);
+ assert.strictEqual(res.status, 200);
+ const clip2 = res.body;
+
+ const note1 = await post(alice, {
+ text: 'baz1',
+ });
+
+ const note2 = await post(alice, {
+ text: 'baz2',
+ });
+
+ res = await api('/clips/add-note', {
+ clipId: clip1.id,
+ noteId: note1.id,
+ }, alice);
+ assert.strictEqual(res.status, 204);
+
+ 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);
+ assert.strictEqual(res.status, 204);
+
+ const exported = await pollFirstDriveFile();
+ assert.strictEqual(exported[0].name, 'kawaii');
+ assert.strictEqual(exported[0].clipNotes.length, 1);
+ assert.strictEqual(exported[0].clipNotes[0].note.text, 'baz1');
+ assert.strictEqual(exported[1].name, 'yuri');
+ assert.strictEqual(exported[1].clipNotes.length, 1);
+ assert.strictEqual(exported[1].clipNotes[0].note.text, 'baz2');
+ });
+
+ test('Clipping other user\'s note', async () => {
+ let res = await api('/clips/create', {
+ name: 'kawaii',
+ description: 'kawaii',
+ }, alice);
+ assert.strictEqual(res.status, 200);
+ const clip = res.body;
+
+ const note = await post(bob, {
+ text: 'baz',
+ visibility: 'followers',
+ });
+
+ 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);
+ assert.strictEqual(res.status, 204);
+
+ const exported = await pollFirstDriveFile();
+ assert.strictEqual(exported[0].name, 'kawaii');
+ assert.strictEqual(exported[0].clipNotes.length, 1);
+ assert.strictEqual(exported[0].clipNotes[0].note.text, 'baz');
+ assert.strictEqual(exported[0].clipNotes[0].note.user.username, 'bob');
+ });
+});
diff --git a/packages/backend/test/e2e/fetch-resource.ts b/packages/backend/test/e2e/fetch-resource.ts
index fb34b60e37..6d88e4bf8c 100644
--- a/packages/backend/test/e2e/fetch-resource.ts
+++ b/packages/backend/test/e2e/fetch-resource.ts
@@ -6,9 +6,8 @@
process.env.NODE_ENV = 'test';
import * as assert from 'assert';
-import { startServer, channel, clip, cookie, galleryPost, signup, page, play, post, simpleGet, uploadFile } from '../utils.js';
+import { channel, clip, cookie, galleryPost, page, play, post, signup, simpleGet, uploadFile } from '../utils.js';
import type { SimpleGetResponse } from '../utils.js';
-import type { INestApplicationContext } from '@nestjs/common';
import type * as misskey from 'cherrypick-js';
// Request Accept
@@ -23,9 +22,7 @@ const HTML = 'text/html; charset=utf-8';
const JSON_UTF8 = 'application/json; charset=utf-8';
describe('Webリソース', () => {
- let app: INestApplicationContext;
-
- let alice: misskey.entities.MeSignup;
+ let alice: misskey.entities.SignupResponse;
let aliceUploadedFile: any;
let alicesPost: any;
let alicePage: any;
@@ -34,7 +31,7 @@ describe('Webリソース', () => {
let aliceGalleryPost: any;
let aliceChannel: any;
- let bob: misskey.entities.MeSignup;
+ let bob: misskey.entities.SignupResponse;
type Request = {
path: string,
@@ -79,7 +76,6 @@ describe('Webリソース', () => {
};
beforeAll(async () => {
- app = await startServer();
alice = await signup({ username: 'alice' });
aliceUploadedFile = await uploadFile(alice);
alicesPost = await post(alice, {
@@ -96,10 +92,6 @@ describe('Webリソース', () => {
bob = await signup({ username: 'bob' });
}, 1000 * 60 * 2);
- afterAll(async () => {
- await app.close();
- });
-
describe.each([
{ path: '/', type: HTML },
{ path: '/docs/ja-JP/about', type: HTML }, // "指定されたURLに該当するページはありませんでした。"
diff --git a/packages/backend/test/e2e/ff-visibility.ts b/packages/backend/test/e2e/ff-visibility.ts
index 2cfdd84dec..97220b5093 100644
--- a/packages/backend/test/e2e/ff-visibility.ts
+++ b/packages/backend/test/e2e/ff-visibility.ts
@@ -6,29 +6,22 @@
process.env.NODE_ENV = 'test';
import * as assert from 'assert';
-import { signup, api, startServer, simpleGet } from '../utils.js';
-import type { INestApplicationContext } from '@nestjs/common';
+import { api, signup, simpleGet } from '../utils.js';
import type * as misskey from 'cherrypick-js';
describe('FF visibility', () => {
- let app: INestApplicationContext;
-
- let alice: misskey.entities.MeSignup;
- let bob: misskey.entities.MeSignup;
+ let alice: misskey.entities.SignupResponse;
+ let bob: misskey.entities.SignupResponse;
beforeAll(async () => {
- app = await startServer();
alice = await signup({ username: 'alice' });
bob = await signup({ username: 'bob' });
}, 1000 * 60 * 2);
- afterAll(async () => {
- await app.close();
- });
-
- test('ffVisibility が public なユーザーのフォロー/フォロワーを誰でも見れる', async () => {
+ test('followingVisibility, followersVisibility がともに public なユーザーのフォロー/フォロワーを誰でも見れる', async () => {
await api('/i/update', {
- ffVisibility: 'public',
+ followingVisibility: 'public',
+ followersVisibility: 'public',
}, alice);
const followingRes = await api('/users/following', {
@@ -44,9 +37,88 @@ describe('FF visibility', () => {
assert.strictEqual(Array.isArray(followersRes.body), true);
});
- test('ffVisibility が followers なユーザーのフォロー/フォロワーを自分で見れる', async () => {
+ test('followingVisibility が public であれば followersVisibility の設定に関わらずユーザーのフォローを誰でも見れる', async () => {
+ {
+ await api('/i/update', {
+ followingVisibility: 'public',
+ followersVisibility: 'public',
+ }, alice);
+
+ 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', {
+ followingVisibility: 'public',
+ followersVisibility: 'followers',
+ }, alice);
+
+ 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', {
+ followingVisibility: 'public',
+ followersVisibility: 'private',
+ }, alice);
+
+ const followingRes = await api('/users/following', {
+ userId: alice.id,
+ }, bob);
+ assert.strictEqual(followingRes.status, 200);
+ assert.strictEqual(Array.isArray(followingRes.body), true);
+ }
+ });
+
+ test('followersVisibility が public であれば followingVisibility の設定に関わらずユーザーのフォロワーを誰でも見れる', async () => {
+ {
+ await api('/i/update', {
+ followingVisibility: 'public',
+ followersVisibility: 'public',
+ }, alice);
+
+ 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', {
+ followingVisibility: 'followers',
+ followersVisibility: 'public',
+ }, alice);
+
+ 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', {
+ followingVisibility: 'private',
+ followersVisibility: 'public',
+ }, alice);
+
+ const followersRes = await api('/users/followers', {
+ userId: alice.id,
+ }, bob);
+ assert.strictEqual(followersRes.status, 200);
+ assert.strictEqual(Array.isArray(followersRes.body), true);
+ }
+ });
+
+ test('followingVisibility, followersVisibility がともに followers なユーザーのフォロー/フォロワーを自分で見れる', async () => {
await api('/i/update', {
- ffVisibility: 'followers',
+ followingVisibility: 'followers',
+ followersVisibility: 'followers',
}, alice);
const followingRes = await api('/users/following', {
@@ -62,9 +134,88 @@ describe('FF visibility', () => {
assert.strictEqual(Array.isArray(followersRes.body), true);
});
- test('ffVisibility が followers なユーザーのフォロー/フォロワーを非フォロワーが見れない', async () => {
+ test('followingVisibility が followers なユーザーのフォローを followersVisibility の設定に関わらず自分で見れる', async () => {
+ {
+ await api('/i/update', {
+ followingVisibility: 'followers',
+ followersVisibility: 'public',
+ }, alice);
+
+ 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', {
+ followingVisibility: 'followers',
+ followersVisibility: 'followers',
+ }, alice);
+
+ 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', {
+ followingVisibility: 'followers',
+ followersVisibility: 'private',
+ }, alice);
+
+ const followingRes = await api('/users/following', {
+ userId: alice.id,
+ }, alice);
+ assert.strictEqual(followingRes.status, 200);
+ assert.strictEqual(Array.isArray(followingRes.body), true);
+ }
+ });
+
+ test('followersVisibility が followers なユーザーのフォロワーを followingVisibility の設定に関わらず自分で見れる', async () => {
+ {
+ await api('/i/update', {
+ followingVisibility: 'public',
+ followersVisibility: 'followers',
+ }, alice);
+
+ 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', {
+ followingVisibility: 'followers',
+ followersVisibility: 'followers',
+ }, alice);
+
+ 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', {
+ followingVisibility: 'private',
+ followersVisibility: 'followers',
+ }, alice);
+
+ const followersRes = await api('/users/followers', {
+ userId: alice.id,
+ }, alice);
+ assert.strictEqual(followersRes.status, 200);
+ assert.strictEqual(Array.isArray(followersRes.body), true);
+ }
+ });
+
+ test('followingVisibility, followersVisibility がともに followers なユーザーのフォロー/フォロワーを非フォロワーが見れない', async () => {
await api('/i/update', {
- ffVisibility: 'followers',
+ followingVisibility: 'followers',
+ followersVisibility: 'followers',
}, alice);
const followingRes = await api('/users/following', {
@@ -78,9 +229,82 @@ describe('FF visibility', () => {
assert.strictEqual(followersRes.status, 400);
});
- test('ffVisibility が followers なユーザーのフォロー/フォロワーをフォロワーが見れる', async () => {
+ test('followingVisibility が followers なユーザーのフォローを followersVisibility の設定に関わらず非フォロワーが見れない', async () => {
+ {
+ await api('/i/update', {
+ followingVisibility: 'followers',
+ followersVisibility: 'public',
+ }, alice);
+
+ const followingRes = await api('/users/following', {
+ userId: alice.id,
+ }, bob);
+ assert.strictEqual(followingRes.status, 400);
+ }
+ {
+ await api('/i/update', {
+ followingVisibility: 'followers',
+ followersVisibility: 'followers',
+ }, alice);
+
+ const followingRes = await api('/users/following', {
+ userId: alice.id,
+ }, bob);
+ assert.strictEqual(followingRes.status, 400);
+ }
+ {
+ await api('/i/update', {
+ followingVisibility: 'followers',
+ followersVisibility: 'private',
+ }, alice);
+
+ const followingRes = await api('/users/following', {
+ userId: alice.id,
+ }, bob);
+ assert.strictEqual(followingRes.status, 400);
+ }
+ });
+
+ test('followersVisibility が followers なユーザーのフォロワーを followingVisibility の設定に関わらず非フォロワーが見れない', async () => {
+ {
+ await api('/i/update', {
+ followingVisibility: 'public',
+ followersVisibility: 'followers',
+ }, alice);
+
+ const followersRes = await api('/users/followers', {
+ userId: alice.id,
+ }, bob);
+ assert.strictEqual(followersRes.status, 400);
+ }
+ {
+ await api('/i/update', {
+ followingVisibility: 'followers',
+ followersVisibility: 'followers',
+ }, alice);
+
+ const followersRes = await api('/users/followers', {
+ userId: alice.id,
+ }, bob);
+ assert.strictEqual(followersRes.status, 400);
+ }
+ {
+ await api('/i/update', {
+ followingVisibility: 'private',
+ followersVisibility: 'followers',
+ }, alice);
+
+ const followersRes = await api('/users/followers', {
+ userId: alice.id,
+ }, bob);
+ assert.strictEqual(followersRes.status, 400);
+ }
+ });
+
+ test('followingVisibility, followersVisibility がともに followers なユーザーのフォロー/フォロワーをフォロワーが見れる', async () => {
await api('/i/update', {
- ffVisibility: 'followers',
+ followingVisibility: 'followers',
+ followersVisibility: 'followers',
}, alice);
await api('/following/create', {
@@ -100,9 +324,106 @@ describe('FF visibility', () => {
assert.strictEqual(Array.isArray(followersRes.body), true);
});
- test('ffVisibility が private なユーザーのフォロー/フォロワーを自分で見れる', async () => {
+ test('followingVisibility が followers なユーザーのフォローを followersVisibility の設定に関わらずフォロワーが見れる', async () => {
+ {
+ await api('/i/update', {
+ followingVisibility: 'followers',
+ followersVisibility: 'public',
+ }, alice);
+ await api('/following/create', {
+ userId: alice.id,
+ }, bob);
+
+ 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', {
+ followingVisibility: 'followers',
+ followersVisibility: 'followers',
+ }, alice);
+ await api('/following/create', {
+ userId: alice.id,
+ }, bob);
+
+ 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', {
+ followingVisibility: 'followers',
+ followersVisibility: 'private',
+ }, alice);
+ await api('/following/create', {
+ userId: alice.id,
+ }, bob);
+
+ const followingRes = await api('/users/following', {
+ userId: alice.id,
+ }, bob);
+ assert.strictEqual(followingRes.status, 200);
+ assert.strictEqual(Array.isArray(followingRes.body), true);
+ }
+ });
+
+ test('followersVisibility が followers なユーザーのフォロワーを followingVisibility の設定に関わらずフォロワーが見れる', async () => {
+ {
+ await api('/i/update', {
+ followingVisibility: 'public',
+ followersVisibility: 'followers',
+ }, alice);
+ await api('/following/create', {
+ userId: alice.id,
+ }, bob);
+
+ 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', {
+ followingVisibility: 'followers',
+ followersVisibility: 'followers',
+ }, alice);
+ await api('/following/create', {
+ userId: alice.id,
+ }, bob);
+
+ 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', {
+ followingVisibility: 'private',
+ followersVisibility: 'followers',
+ }, alice);
+ await api('/following/create', {
+ userId: alice.id,
+ }, bob);
+
+ const followersRes = await api('/users/followers', {
+ userId: alice.id,
+ }, bob);
+ assert.strictEqual(followersRes.status, 200);
+ assert.strictEqual(Array.isArray(followersRes.body), true);
+ }
+ });
+
+ test('followingVisibility, followersVisibility がともに private なユーザーのフォロー/フォロワーを自分で見れる', async () => {
await api('/i/update', {
- ffVisibility: 'private',
+ followingVisibility: 'private',
+ followersVisibility: 'private',
}, alice);
const followingRes = await api('/users/following', {
@@ -118,9 +439,88 @@ describe('FF visibility', () => {
assert.strictEqual(Array.isArray(followersRes.body), true);
});
- test('ffVisibility が private なユーザーのフォロー/フォロワーを他人が見れない', async () => {
+ test('followingVisibility が private なユーザーのフォローを followersVisibility の設定に関わらず自分で見れる', async () => {
+ {
+ await api('/i/update', {
+ followingVisibility: 'private',
+ followersVisibility: 'public',
+ }, alice);
+
+ 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', {
+ followingVisibility: 'private',
+ followersVisibility: 'followers',
+ }, alice);
+
+ 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', {
+ followingVisibility: 'private',
+ followersVisibility: 'private',
+ }, alice);
+
+ const followingRes = await api('/users/following', {
+ userId: alice.id,
+ }, alice);
+ assert.strictEqual(followingRes.status, 200);
+ assert.strictEqual(Array.isArray(followingRes.body), true);
+ }
+ });
+
+ test('followersVisibility が private なユーザーのフォロワーを followingVisibility の設定に関わらず自分で見れる', async () => {
+ {
+ await api('/i/update', {
+ followingVisibility: 'public',
+ followersVisibility: 'private',
+ }, alice);
+
+ 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', {
+ followingVisibility: 'followers',
+ followersVisibility: 'private',
+ }, alice);
+
+ 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', {
+ followingVisibility: 'private',
+ followersVisibility: 'private',
+ }, alice);
+
+ const followersRes = await api('/users/followers', {
+ userId: alice.id,
+ }, alice);
+ assert.strictEqual(followersRes.status, 200);
+ assert.strictEqual(Array.isArray(followersRes.body), true);
+ }
+ });
+
+ test('followingVisibility, followersVisibility がともに private なユーザーのフォロー/フォロワーを他人が見れない', async () => {
await api('/i/update', {
- ffVisibility: 'private',
+ followingVisibility: 'private',
+ followersVisibility: 'private',
}, alice);
const followingRes = await api('/users/following', {
@@ -134,36 +534,129 @@ describe('FF visibility', () => {
assert.strictEqual(followersRes.status, 400);
});
+ test('followingVisibility が private なユーザーのフォローを followersVisibility の設定に関わらず他人が見れない', async () => {
+ {
+ await api('/i/update', {
+ followingVisibility: 'private',
+ followersVisibility: 'public',
+ }, alice);
+
+ const followingRes = await api('/users/following', {
+ userId: alice.id,
+ }, bob);
+ assert.strictEqual(followingRes.status, 400);
+ }
+ {
+ await api('/i/update', {
+ followingVisibility: 'private',
+ followersVisibility: 'followers',
+ }, alice);
+
+ const followingRes = await api('/users/following', {
+ userId: alice.id,
+ }, bob);
+ assert.strictEqual(followingRes.status, 400);
+ }
+ {
+ await api('/i/update', {
+ followingVisibility: 'private',
+ followersVisibility: 'private',
+ }, alice);
+
+ const followingRes = await api('/users/following', {
+ userId: alice.id,
+ }, bob);
+ assert.strictEqual(followingRes.status, 400);
+ }
+ });
+
+ test('followersVisibility が private なユーザーのフォロワーを followingVisibility の設定に関わらず他人が見れない', async () => {
+ {
+ await api('/i/update', {
+ followingVisibility: 'public',
+ followersVisibility: 'private',
+ }, alice);
+
+ const followersRes = await api('/users/followers', {
+ userId: alice.id,
+ }, bob);
+ assert.strictEqual(followersRes.status, 400);
+ }
+ {
+ await api('/i/update', {
+ followingVisibility: 'followers',
+ followersVisibility: 'private',
+ }, alice);
+
+ const followersRes = await api('/users/followers', {
+ userId: alice.id,
+ }, bob);
+ assert.strictEqual(followersRes.status, 400);
+ }
+ {
+ await api('/i/update', {
+ followingVisibility: 'private',
+ followersVisibility: 'private',
+ }, alice);
+
+ const followersRes = await api('/users/followers', {
+ userId: alice.id,
+ }, bob);
+ assert.strictEqual(followersRes.status, 400);
+ }
+ });
+
describe('AP', () => {
- test('ffVisibility が public 以外ならばAPからは取得できない', async () => {
+ test('followingVisibility が public 以外ならばAPからはフォローを取得できない', async () => {
{
await api('/i/update', {
- ffVisibility: 'public',
+ followingVisibility: 'public',
}, alice);
const followingRes = await simpleGet(`/users/${alice.id}/following`, 'application/activity+json');
- const followersRes = await simpleGet(`/users/${alice.id}/followers`, 'application/activity+json');
assert.strictEqual(followingRes.status, 200);
- assert.strictEqual(followersRes.status, 200);
}
{
await api('/i/update', {
- ffVisibility: 'followers',
+ followingVisibility: 'followers',
}, alice);
const followingRes = await simpleGet(`/users/${alice.id}/following`, 'application/activity+json');
- const followersRes = await simpleGet(`/users/${alice.id}/followers`, 'application/activity+json');
assert.strictEqual(followingRes.status, 403);
- assert.strictEqual(followersRes.status, 403);
}
{
await api('/i/update', {
- ffVisibility: 'private',
+ followingVisibility: 'private',
}, alice);
const followingRes = await simpleGet(`/users/${alice.id}/following`, 'application/activity+json');
- const followersRes = await simpleGet(`/users/${alice.id}/followers`, 'application/activity+json');
assert.strictEqual(followingRes.status, 403);
+ }
+ });
+
+ test('followersVisibility が public 以外ならばAPからはフォロワーを取得できない', async () => {
+ {
+ await api('/i/update', {
+ followersVisibility: 'public',
+ }, alice);
+
+ const followersRes = await simpleGet(`/users/${alice.id}/followers`, 'application/activity+json');
+ assert.strictEqual(followersRes.status, 200);
+ }
+ {
+ await api('/i/update', {
+ followersVisibility: 'followers',
+ }, alice);
+
+ const followersRes = await simpleGet(`/users/${alice.id}/followers`, 'application/activity+json');
+ assert.strictEqual(followersRes.status, 403);
+ }
+ {
+ await api('/i/update', {
+ followersVisibility: 'private',
+ }, alice);
+
+ const followersRes = await simpleGet(`/users/${alice.id}/followers`, 'application/activity+json');
assert.strictEqual(followersRes.status, 403);
}
});
diff --git a/packages/backend/test/e2e/move.ts b/packages/backend/test/e2e/move.ts
index ca3f825f56..86e0cc57b6 100644
--- a/packages/backend/test/e2e/move.ts
+++ b/packages/backend/test/e2e/move.ts
@@ -3,35 +3,35 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
+import { INestApplicationContext } from '@nestjs/common';
+
process.env.NODE_ENV = 'test';
import * as assert from 'assert';
import { loadConfig } from '@/config.js';
import { MiUser, UsersRepository } from '@/models/_.js';
-import { jobQueue } from '@/boot/common.js';
import { secureRndstr } from '@/misc/secure-rndstr.js';
-import { uploadFile, signup, startServer, initTestDb, api, sleep, successfulApiCall } from '../utils.js';
-import type { INestApplicationContext } from '@nestjs/common';
+import { jobQueue } from '@/boot/common.js';
+import { api, initTestDb, signup, sleep, successfulApiCall, uploadFile } from '../utils.js';
import type * as misskey from 'cherrypick-js';
describe('Account Move', () => {
- let app: INestApplicationContext;
let jq: INestApplicationContext;
let url: URL;
let root: any;
- let alice: misskey.entities.MeSignup;
- let bob: misskey.entities.MeSignup;
- let carol: misskey.entities.MeSignup;
- let dave: misskey.entities.MeSignup;
- let eve: misskey.entities.MeSignup;
- let frank: misskey.entities.MeSignup;
+ let alice: misskey.entities.SignupResponse;
+ let bob: misskey.entities.SignupResponse;
+ let carol: misskey.entities.SignupResponse;
+ let dave: misskey.entities.SignupResponse;
+ let eve: misskey.entities.SignupResponse;
+ let frank: misskey.entities.SignupResponse;
let Users: UsersRepository;
beforeAll(async () => {
- app = await startServer();
jq = await jobQueue();
+
const config = loadConfig();
url = new URL(config.url);
const connection = await initTestDb(false);
@@ -46,7 +46,7 @@ describe('Account Move', () => {
}, 1000 * 60 * 2);
afterAll(async () => {
- await Promise.all([app.close(), jq.close()]);
+ await jq.close();
});
describe('Create Alias', () => {
diff --git a/packages/backend/test/e2e/mute.ts b/packages/backend/test/e2e/mute.ts
index 2c4e59b5e5..b231bf821f 100644
--- a/packages/backend/test/e2e/mute.ts
+++ b/packages/backend/test/e2e/mute.ts
@@ -6,29 +6,21 @@
process.env.NODE_ENV = 'test';
import * as assert from 'assert';
-import { signup, api, post, react, startServer, waitFire } from '../utils.js';
-import type { INestApplicationContext } from '@nestjs/common';
+import { api, post, react, signup, waitFire } from '../utils.js';
import type * as misskey from 'cherrypick-js';
describe('Mute', () => {
- let app: INestApplicationContext;
-
// alice mutes carol
- let alice: misskey.entities.MeSignup;
- let bob: misskey.entities.MeSignup;
- let carol: misskey.entities.MeSignup;
+ let alice: misskey.entities.SignupResponse;
+ let bob: misskey.entities.SignupResponse;
+ let carol: misskey.entities.SignupResponse;
beforeAll(async () => {
- app = await startServer();
alice = await signup({ username: 'alice' });
bob = await signup({ username: 'bob' });
carol = await signup({ username: 'carol' });
}, 1000 * 60 * 2);
- afterAll(async () => {
- await app.close();
- });
-
test('ミュート作成', async () => {
const res = await api('/mute/create', {
userId: carol.id,
diff --git a/packages/backend/test/e2e/nodeinfo.ts b/packages/backend/test/e2e/nodeinfo.ts
new file mode 100644
index 0000000000..a9e3f14624
--- /dev/null
+++ b/packages/backend/test/e2e/nodeinfo.ts
@@ -0,0 +1,29 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey, cherrypick contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+process.env.NODE_ENV = 'test';
+
+import * as assert from 'assert';
+import { relativeFetch } from '../utils.js';
+
+describe('nodeinfo', () => {
+ test('nodeinfo 2.1', async () => {
+ const res = await relativeFetch('nodeinfo/2.1');
+ assert.ok(res.ok);
+ assert.strictEqual(res.headers.get('Access-Control-Allow-Origin'), '*');
+
+ const nodeInfo = await res.json() as any;
+ assert.strictEqual(nodeInfo.software.name, 'cherrypick');
+ });
+
+ test('nodeinfo 2.0', async () => {
+ const res = await relativeFetch('nodeinfo/2.0');
+ assert.ok(res.ok);
+ assert.strictEqual(res.headers.get('Access-Control-Allow-Origin'), '*');
+
+ const nodeInfo = await res.json() as any;
+ assert.strictEqual(nodeInfo.software.name, 'cherrypick');
+ });
+});
diff --git a/packages/backend/test/e2e/note.ts b/packages/backend/test/e2e/note.ts
index 179bb80398..a46e63f39c 100644
--- a/packages/backend/test/e2e/note.ts
+++ b/packages/backend/test/e2e/note.ts
@@ -8,29 +8,22 @@ process.env.NODE_ENV = 'test';
import * as assert from 'assert';
import { MiNote } from '@/models/Note.js';
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
-import { signup, post, uploadUrl, startServer, initTestDb, api, uploadFile } from '../utils.js';
-import type { INestApplicationContext } from '@nestjs/common';
+import { api, initTestDb, post, signup, uploadFile, uploadUrl } from '../utils.js';
import type * as misskey from 'cherrypick-js';
describe('Note', () => {
- let app: INestApplicationContext;
let Notes: any;
- let alice: misskey.entities.MeSignup;
- let bob: misskey.entities.MeSignup;
+ let alice: misskey.entities.SignupResponse;
+ let bob: misskey.entities.SignupResponse;
beforeAll(async () => {
- app = await startServer();
const connection = await initTestDb(true);
Notes = connection.getRepository(MiNote);
alice = await signup({ username: 'alice' });
bob = await signup({ username: 'bob' });
}, 1000 * 60 * 2);
- afterAll(async () => {
- await app.close();
- });
-
test('投稿できる', async () => {
const post = {
text: 'test',
diff --git a/packages/backend/test/e2e/oauth.ts b/packages/backend/test/e2e/oauth.ts
index 5e00ca1483..727788028a 100644
--- a/packages/backend/test/e2e/oauth.ts
+++ b/packages/backend/test/e2e/oauth.ts
@@ -4,20 +4,25 @@
*/
/**
- * Basic OAuth tests to make sure the library is correctly integrated to Misskey
+ * Basic OAuth tests to make sure the library is correctly integrated to CherryPick
* and not regressed by version updates or potential migration to another library.
*/
process.env.NODE_ENV = 'test';
import * as assert from 'assert';
-import { AuthorizationCode, ResourceOwnerPassword, type AuthorizationTokenConfig, ClientCredentials, ModuleOptions } from 'simple-oauth2';
+import {
+ AuthorizationCode,
+ type AuthorizationTokenConfig,
+ ClientCredentials,
+ ModuleOptions,
+ ResourceOwnerPassword,
+} from 'simple-oauth2';
import pkceChallenge from 'pkce-challenge';
import { JSDOM } from 'jsdom';
-import Fastify, { type FastifyReply, type FastifyInstance } from 'fastify';
-import { api, port, signup, startServer } from '../utils.js';
+import Fastify, { type FastifyInstance, type FastifyReply } from 'fastify';
+import { api, port, sendEnvUpdateRequest, signup } from '../utils.js';
import type * as misskey from 'cherrypick-js';
-import type { INestApplicationContext } from '@nestjs/common';
const host = `http://127.0.0.1:${port}`;
@@ -75,7 +80,7 @@ function getMeta(html: string): { transactionId: string | undefined, clientName:
};
}
-function fetchDecision(transactionId: string, user: misskey.entities.MeSignup, { cancel }: { cancel?: boolean } = {}): Promise {
+function fetchDecision(transactionId: string, user: misskey.entities.SignupResponse, { cancel }: { cancel?: boolean } = {}): Promise {
return fetch(new URL('/oauth/decision', host), {
method: 'post',
body: new URLSearchParams({
@@ -90,14 +95,14 @@ function fetchDecision(transactionId: string, user: misskey.entities.MeSignup, {
});
}
-async function fetchDecisionFromResponse(response: Response, user: misskey.entities.MeSignup, { cancel }: { cancel?: boolean } = {}): Promise {
+async function fetchDecisionFromResponse(response: Response, user: misskey.entities.SignupResponse, { cancel }: { cancel?: boolean } = {}): Promise {
const { transactionId } = getMeta(await response.text());
assert.ok(transactionId);
return await fetchDecision(transactionId, user, { cancel });
}
-async function fetchAuthorizationCode(user: misskey.entities.MeSignup, scope: string, code_challenge: string): Promise<{ client: AuthorizationCode, code: string }> {
+async function fetchAuthorizationCode(user: misskey.entities.SignupResponse, scope: string, code_challenge: string): Promise<{ client: AuthorizationCode, code: string }> {
const client = new AuthorizationCode(clientConfig);
const response = await fetch(client.authorizeURL({
@@ -134,7 +139,7 @@ function assertIndirectError(response: Response, error: string): void {
assert.strictEqual(location.searchParams.get('error'), error);
// https://datatracker.ietf.org/doc/html/rfc9207#name-response-parameter-iss
- assert.strictEqual(location.searchParams.get('iss'), 'http://misskey.local');
+ assert.strictEqual(location.searchParams.get('iss'), 'http://cherrypick.local');
// https://datatracker.ietf.org/doc/html/rfc6749.html#section-4.1.2.1
assert.ok(location.searchParams.has('state'));
}
@@ -147,16 +152,14 @@ async function assertDirectError(response: Response, status: number, error: stri
}
describe('OAuth', () => {
- let app: INestApplicationContext;
let fastify: FastifyInstance;
- let alice: misskey.entities.MeSignup;
- let bob: misskey.entities.MeSignup;
+ let alice: misskey.entities.SignupResponse;
+ let bob: misskey.entities.SignupResponse;
let sender: (reply: FastifyReply) => void;
beforeAll(async () => {
- app = await startServer();
alice = await signup({ username: 'alice' });
bob = await signup({ username: 'bob' });
@@ -168,19 +171,18 @@ describe('OAuth', () => {
}, 1000 * 60 * 2);
beforeEach(async () => {
- process.env.MISSKEY_TEST_CHECK_IP_RANGE = '';
+ await sendEnvUpdateRequest({ key: 'CHERRYPICK_TEST_CHECK_IP_RANGE', value: '' });
sender = (reply): void => {
reply.send(`
- Misklient
+ Crpklient
`);
};
});
afterAll(async () => {
await fastify.close();
- await app.close();
});
test('Full flow', async () => {
@@ -200,7 +202,7 @@ describe('OAuth', () => {
const meta = getMeta(await response.text());
assert.strictEqual(typeof meta.transactionId, 'string');
assert.ok(meta.transactionId);
- assert.strictEqual(meta.clientName, 'Misklient');
+ assert.strictEqual(meta.clientName, 'Crpklient');
const decisionResponse = await fetchDecision(meta.transactionId, alice);
assert.strictEqual(decisionResponse.status, 302);
@@ -812,7 +814,7 @@ describe('OAuth', () => {
reply.header('Link', '; rel="redirect_uri"');
reply.send(`
- Misklient
+ Crpklient
`);
},
'Mixed links': reply => {
@@ -820,14 +822,14 @@ describe('OAuth', () => {
reply.send(`
- Misklient
+ Crpklient
`);
},
'Multiple items in Link header': reply => {
reply.header('Link', '; rel="redirect_uri",; rel="redirect_uri"');
reply.send(`
- Misklient
+ Crpklient
`);
},
'Multiple items in HTML': reply => {
@@ -835,7 +837,7 @@ describe('OAuth', () => {
- Misklient
+ Crpklient
`);
},
};
@@ -861,7 +863,7 @@ describe('OAuth', () => {
sender = (reply): void => {
reply.send(`
- Misklient
+ Crpklient
`);
};
@@ -881,7 +883,7 @@ describe('OAuth', () => {
});
test('Disallow loopback', async () => {
- process.env.MISSKEY_TEST_CHECK_IP_RANGE = '1';
+ await sendEnvUpdateRequest({ key: 'CHERRYPICK_TEST_CHECK_IP_RANGE', value: '1' });
const client = new AuthorizationCode(clientConfig);
const response = await fetch(client.authorizeURL({
@@ -918,7 +920,7 @@ describe('OAuth', () => {
reply.header('Link', '; rel="redirect_uri"');
reply.send(`
- Misklient
+ Crpklient
`);
reply.send();
};
@@ -941,4 +943,24 @@ describe('OAuth', () => {
const response = await fetch(new URL('/oauth/foo', host));
assert.strictEqual(response.status, 404);
});
+
+ describe('CORS', () => {
+ test('Token endpoint should support CORS', async () => {
+ const response = await fetch(new URL('/oauth/token', host), { method: 'POST' });
+ assert.ok(!response.ok);
+ assert.strictEqual(response.headers.get('Access-Control-Allow-Origin'), '*');
+ });
+
+ test('Authorize endpoint should not support CORS', async () => {
+ const response = await fetch(new URL('/oauth/authorize', host), { method: 'GET' });
+ assert.ok(!response.ok);
+ assert.ok(!response.headers.has('Access-Control-Allow-Origin'));
+ });
+
+ test('Decision endpoint should not support CORS', async () => {
+ const response = await fetch(new URL('/oauth/decision', host), { method: 'POST' });
+ assert.ok(!response.ok);
+ assert.ok(!response.headers.has('Access-Control-Allow-Origin'));
+ });
+ });
});
diff --git a/packages/backend/test/e2e/renote-mute.ts b/packages/backend/test/e2e/renote-mute.ts
index b1e7a745ec..a0771c6741 100644
--- a/packages/backend/test/e2e/renote-mute.ts
+++ b/packages/backend/test/e2e/renote-mute.ts
@@ -6,29 +6,21 @@
process.env.NODE_ENV = 'test';
import * as assert from 'assert';
-import { signup, api, post, react, startServer, waitFire, sleep } from '../utils.js';
-import type { INestApplicationContext } from '@nestjs/common';
+import { api, post, signup, sleep, waitFire } from '../utils.js';
import type * as misskey from 'cherrypick-js';
describe('Renote Mute', () => {
- let app: INestApplicationContext;
-
// alice mutes carol
- let alice: misskey.entities.MeSignup;
- let bob: misskey.entities.MeSignup;
- let carol: misskey.entities.MeSignup;
+ let alice: misskey.entities.SignupResponse;
+ let bob: misskey.entities.SignupResponse;
+ let carol: misskey.entities.SignupResponse;
beforeAll(async () => {
- app = await startServer();
alice = await signup({ username: 'alice' });
bob = await signup({ username: 'bob' });
carol = await signup({ username: 'carol' });
}, 1000 * 60 * 2);
- afterAll(async () => {
- await app.close();
- });
-
test('ミュート作成', async () => {
const res = await api('/renote-mute/create', {
userId: carol.id,
diff --git a/packages/backend/test/e2e/streaming.ts b/packages/backend/test/e2e/streaming.ts
index 9692bc4775..d385d163ac 100644
--- a/packages/backend/test/e2e/streaming.ts
+++ b/packages/backend/test/e2e/streaming.ts
@@ -6,13 +6,12 @@
process.env.NODE_ENV = 'test';
import * as assert from 'assert';
+import { WebSocket } from 'ws';
import { MiFollowing } from '@/models/Following.js';
-import { signup, api, post, startServer, initTestDb, waitFire } from '../utils.js';
-import type { INestApplicationContext } from '@nestjs/common';
+import { api, createAppToken, initTestDb, port, post, signup, waitFire } from '../utils.js';
import type * as misskey from 'cherrypick-js';
describe('Streaming', () => {
- let app: INestApplicationContext;
let Followings: any;
const follow = async (follower: any, followee: any) => {
@@ -31,15 +30,15 @@ describe('Streaming', () => {
describe('Streaming', () => {
// Local users
- let ayano: misskey.entities.MeSignup;
- let kyoko: misskey.entities.MeSignup;
- let chitose: misskey.entities.MeSignup;
- let kanako: misskey.entities.MeSignup;
+ let ayano: misskey.entities.SignupResponse;
+ let kyoko: misskey.entities.SignupResponse;
+ let chitose: misskey.entities.SignupResponse;
+ let kanako: misskey.entities.SignupResponse;
// Remote users
- let akari: misskey.entities.MeSignup;
- let chinatsu: misskey.entities.MeSignup;
- let takumi: misskey.entities.MeSignup;
+ let akari: misskey.entities.SignupResponse;
+ let chinatsu: misskey.entities.SignupResponse;
+ let takumi: misskey.entities.SignupResponse;
let kyokoNote: any;
let kanakoNote: any;
@@ -47,7 +46,6 @@ describe('Streaming', () => {
let list: any;
beforeAll(async () => {
- app = await startServer();
const connection = await initTestDb(true);
Followings = connection.getRepository(MiFollowing);
@@ -94,10 +92,6 @@ describe('Streaming', () => {
}, chitose);
}, 1000 * 60 * 2);
- afterAll(async () => {
- await app.close();
- });
-
describe('Events', () => {
test('mention event', async () => {
const fired = await waitFire(
@@ -560,6 +554,28 @@ describe('Streaming', () => {
});
});
+ test('Authentication', async () => {
+ const application = await createAppToken(ayano, []);
+ const application2 = await createAppToken(ayano, ['read:account']);
+ const socket = new WebSocket(`ws://127.0.0.1:${port}/streaming?i=${application}`);
+ const established = await new Promise((resolve, reject) => {
+ socket.on('error', () => resolve(false));
+ socket.on('unexpected-response', () => resolve(false));
+ setTimeout(() => resolve(true), 3000);
+ });
+
+ socket.close();
+ assert.strictEqual(established, false);
+
+ const fired = await waitFire(
+ { token: application2 }, 'hybridTimeline',
+ () => api('notes/create', { text: 'Hello, world!' }, ayano),
+ msg => msg.type === 'note' && msg.body.userId === ayano.id,
+ );
+
+ assert.strictEqual(fired, true);
+ });
+
// XXX: QueryFailedError: duplicate key value violates unique constraint "IDX_347fec870eafea7b26c8a73bac"
/*
describe('Hashtag Timeline', () => {
diff --git a/packages/backend/test/e2e/thread-mute.ts b/packages/backend/test/e2e/thread-mute.ts
index 9f50055259..c0812fc6e4 100644
--- a/packages/backend/test/e2e/thread-mute.ts
+++ b/packages/backend/test/e2e/thread-mute.ts
@@ -6,28 +6,20 @@
process.env.NODE_ENV = 'test';
import * as assert from 'assert';
-import { signup, api, post, connectStream, startServer } from '../utils.js';
-import type { INestApplicationContext } from '@nestjs/common';
+import { api, connectStream, post, signup } from '../utils.js';
import type * as misskey from 'cherrypick-js';
describe('Note thread mute', () => {
- let app: INestApplicationContext;
-
- let alice: misskey.entities.MeSignup;
- let bob: misskey.entities.MeSignup;
- let carol: misskey.entities.MeSignup;
+ let alice: misskey.entities.SignupResponse;
+ let bob: misskey.entities.SignupResponse;
+ let carol: misskey.entities.SignupResponse;
beforeAll(async () => {
- app = await startServer();
alice = await signup({ username: 'alice' });
bob = await signup({ username: 'bob' });
carol = await signup({ username: 'carol' });
}, 1000 * 60 * 2);
- afterAll(async () => {
- await app.close();
- });
-
test('notes/mentions にミュートしているスレッドの投稿が含まれない', async () => {
const bobNote = await post(bob, { text: '@alice @carol root note' });
const aliceReply = await post(alice, { replyId: bobNote.id, text: '@bob @carol child note' });
diff --git a/packages/backend/test/e2e/timelines.ts b/packages/backend/test/e2e/timelines.ts
index 94956047d4..1efe2609e0 100644
--- a/packages/backend/test/e2e/timelines.ts
+++ b/packages/backend/test/e2e/timelines.ts
@@ -6,13 +6,8 @@
// How to run:
// pnpm jest -- e2e/timelines.ts
-process.env.NODE_ENV = 'test';
-process.env.FORCE_FOLLOW_REMOTE_USER_FOR_TESTING = 'true';
-
import * as assert from 'assert';
-import { signup, api, post, react, startServer, waitFire, sleep, uploadUrl, randomString } from '../utils.js';
-import type { INestApplicationContext } from '@nestjs/common';
-import type * as misskey from 'cherrypick-js';
+import { api, post, randomString, sendEnvUpdateRequest, signup, sleep, uploadUrl } from '../utils.js';
function genHost() {
return randomString() + '.example.com';
@@ -22,16 +17,6 @@ function waitForPushToTl() {
return sleep(500);
}
-let app: INestApplicationContext;
-
-beforeAll(async () => {
- app = await startServer();
-}, 1000 * 60 * 2);
-
-afterAll(async () => {
- await app.close();
-});
-
describe('Timelines', () => {
describe('Home TL', () => {
test.concurrent('自分の visibility: followers なノートが含まれる', async () => {
@@ -335,8 +320,9 @@ describe('Timelines', () => {
test.concurrent('フォローしているリモートユーザーのノートが含まれる', async () => {
const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
+ await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' });
await api('/following/create', { userId: bob.id }, alice);
- await sleep(1000);
+
const bobNote = await post(bob, { text: 'hi' });
await waitForPushToTl();
@@ -349,8 +335,9 @@ describe('Timelines', () => {
test.concurrent('フォローしているリモートユーザーの visibility: home なノートが含まれる', async () => {
const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
+ await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' });
await api('/following/create', { userId: bob.id }, alice);
- await sleep(1000);
+
const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
await waitForPushToTl();
@@ -366,8 +353,8 @@ describe('Timelines', () => {
await api('/following/create', { userId: bob.id }, alice);
await sleep(1000);
const [bobFile, carolFile] = await Promise.all([
- uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/icon.png'),
- uploadUrl(carol, 'https://raw.githubusercontent.com/misskey-dev/assets/main/icon.png'),
+ uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png'),
+ uploadUrl(carol, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png'),
]);
const bobNote1 = await post(bob, { text: 'hi' });
const bobNote2 = await post(bob, { fileIds: [bobFile.id] });
@@ -666,7 +653,7 @@ describe('Timelines', () => {
test.concurrent('[withFiles: true] ファイル付きノートのみ含まれる', async () => {
const [alice, bob] = await Promise.all([signup(), signup()]);
- const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/icon.png');
+ const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png');
const bobNote1 = await post(bob, { text: 'hi' });
const bobNote2 = await post(bob, { fileIds: [file.id] });
@@ -763,8 +750,9 @@ describe('Timelines', () => {
test.concurrent('フォローしているリモートユーザーのノートが含まれる', async () => {
const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
+ await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' });
await api('/following/create', { userId: bob.id }, alice);
- await sleep(1000);
+
const bobNote = await post(bob, { text: 'hi' });
await waitForPushToTl();
@@ -777,8 +765,9 @@ describe('Timelines', () => {
test.concurrent('フォローしているリモートユーザーの visibility: home なノートが含まれる', async () => {
const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
+ await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' });
await api('/following/create', { userId: bob.id }, alice);
- await sleep(1000);
+
const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
await waitForPushToTl();
@@ -804,7 +793,7 @@ describe('Timelines', () => {
test.concurrent('[withFiles: true] ファイル付きノートのみ含まれる', async () => {
const [alice, bob] = await Promise.all([signup(), signup()]);
- const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/icon.png');
+ const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png');
const bobNote1 = await post(bob, { text: 'hi' });
const bobNote2 = await post(bob, { fileIds: [file.id] });
@@ -999,7 +988,7 @@ describe('Timelines', () => {
const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
- const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/icon.png');
+ const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png');
const bobNote1 = await post(bob, { text: 'hi' });
const bobNote2 = await post(bob, { fileIds: [file.id] });
@@ -1158,7 +1147,7 @@ describe('Timelines', () => {
test.concurrent('[withFiles: true] ファイル付きノートのみ含まれる', async () => {
const [alice, bob] = await Promise.all([signup(), signup()]);
- const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/icon.png');
+ const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png');
const bobNote1 = await post(bob, { text: 'hi' });
const bobNote2 = await post(bob, { fileIds: [file.id] });
diff --git a/packages/backend/test/e2e/user-notes.ts b/packages/backend/test/e2e/user-notes.ts
index 811ffc4b6e..9d319270dc 100644
--- a/packages/backend/test/e2e/user-notes.ts
+++ b/packages/backend/test/e2e/user-notes.ts
@@ -6,20 +6,16 @@
process.env.NODE_ENV = 'test';
import * as assert from 'assert';
-import { signup, api, post, uploadUrl, startServer } from '../utils.js';
-import type { INestApplicationContext } from '@nestjs/common';
+import { api, post, signup, uploadUrl } from '../utils.js';
import type * as misskey from 'cherrypick-js';
describe('users/notes', () => {
- let app: INestApplicationContext;
-
- let alice: misskey.entities.MeSignup;
+ let alice: misskey.entities.SignupResponse;
let jpgNote: any;
let pngNote: any;
let jpgPngNote: any;
beforeAll(async () => {
- app = await startServer();
alice = await signup({ username: 'alice' });
const jpg = await uploadUrl(alice, 'https://raw.githubusercontent.com/kokonect-link/cherrypick/develop/packages/backend/test/resources/Lenna.jpg');
const png = await uploadUrl(alice, 'https://raw.githubusercontent.com/kokonect-link/cherrypick/develop/packages/backend/test/resources/Lenna.png');
@@ -34,10 +30,6 @@ describe('users/notes', () => {
});
}, 1000 * 60 * 2);
- afterAll(async() => {
- await app.close();
- });
-
test('withFiles', async () => {
const res = await api('/users/notes', {
userId: alice.id,
diff --git a/packages/backend/test/e2e/users.ts b/packages/backend/test/e2e/users.ts
index 93cee0a8dd..af66afd33a 100644
--- a/packages/backend/test/e2e/users.ts
+++ b/packages/backend/test/e2e/users.ts
@@ -8,20 +8,8 @@ process.env.NODE_ENV = 'test';
import * as assert from 'assert';
import { inspect } from 'node:util';
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
-import type { Packed } from '@/misc/json-schema.js';
-import {
- signup,
- post,
- page,
- role,
- startServer,
- api,
- successfulApiCall,
- failedApiCall,
- uploadFile,
-} from '../utils.js';
+import { api, page, post, role, signup, successfulApiCall, uploadFile } from '../utils.js';
import type * as misskey from 'cherrypick-js';
-import type { INestApplicationContext } from '@nestjs/common';
describe('ユーザー', () => {
// エンティティとしてのユーザーを主眼においたテストを記述する
@@ -112,7 +100,8 @@ describe('ユーザー', () => {
pinnedPageId: user.pinnedPageId,
pinnedPage: user.pinnedPage,
publicReactions: user.publicReactions,
- ffVisibility: user.ffVisibility,
+ followingVisibility: user.followingVisibility,
+ followersVisibility: user.followersVisibility,
twoFactorEnabled: user.twoFactorEnabled,
usePasswordLessLogin: user.usePasswordLessLogin,
securityKeys: user.securityKeys,
@@ -185,8 +174,6 @@ describe('ユーザー', () => {
});
};
- let app: INestApplicationContext;
-
let root: User;
let alice: User;
let aliceNote: misskey.entities.Note;
@@ -230,10 +217,6 @@ describe('ユーザー', () => {
let userFollowRequesting: User;
let userFollowRequested: User;
- beforeAll(async () => {
- app = await startServer();
- }, 1000 * 60 * 2);
-
beforeAll(async () => {
root = await signup({ username: 'root' });
alice = await signup({ username: 'alice' });
@@ -321,10 +304,6 @@ describe('ユーザー', () => {
await api('following/create', { userId: userFollowRequested.id }, userFollowRequesting);
}, 1000 * 60 * 10);
- afterAll(async () => {
- await app.close();
- });
-
beforeEach(async () => {
alice = {
...alice,
@@ -387,7 +366,8 @@ describe('ユーザー', () => {
assert.strictEqual(response.pinnedPageId, null);
assert.strictEqual(response.pinnedPage, null);
assert.strictEqual(response.publicReactions, true);
- assert.strictEqual(response.ffVisibility, 'public');
+ assert.strictEqual(response.followingVisibility, 'public');
+ assert.strictEqual(response.followersVisibility, 'public');
assert.strictEqual(response.twoFactorEnabled, false);
assert.strictEqual(response.usePasswordLessLogin, false);
assert.strictEqual(response.securityKeys, false);
@@ -497,9 +477,12 @@ describe('ユーザー', () => {
{ parameters: (): object => ({ alwaysMarkNsfw: false }) },
{ parameters: (): object => ({ autoSensitive: true }) },
{ parameters: (): object => ({ autoSensitive: false }) },
- { parameters: (): object => ({ ffVisibility: 'private' }) },
- { parameters: (): object => ({ ffVisibility: 'followers' }) },
- { parameters: (): object => ({ ffVisibility: 'public' }) },
+ { 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: [] }) },
diff --git a/packages/backend/test/e2e/well-known.ts b/packages/backend/test/e2e/well-known.ts
new file mode 100644
index 0000000000..ede32a03e5
--- /dev/null
+++ b/packages/backend/test/e2e/well-known.ts
@@ -0,0 +1,103 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey, cherrypick contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+process.env.NODE_ENV = 'test';
+
+import * as assert from 'assert';
+import { host, origin, relativeFetch, signup } from '../utils.js';
+import type * as misskey from 'cherrypick-js';
+
+describe('.well-known', () => {
+ let alice: misskey.entities.User;
+
+ beforeAll(async () => {
+ alice = await signup({ username: 'alice' });
+ }, 1000 * 60 * 2);
+
+ test('nodeinfo', async () => {
+ const res = await relativeFetch('.well-known/nodeinfo');
+ assert.ok(res.ok);
+ assert.strictEqual(res.headers.get('Access-Control-Allow-Origin'), '*');
+
+ const nodeInfo = await res.json();
+ assert.deepStrictEqual(nodeInfo, {
+ links: [{
+ rel: 'http://nodeinfo.diaspora.software/ns/schema/2.1',
+ href: `${origin}/nodeinfo/2.1`,
+ }, {
+ rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0',
+ href: `${origin}/nodeinfo/2.0`,
+ }],
+ });
+ });
+
+ test('webfinger', async () => {
+ const preflight = await relativeFetch(`.well-known/webfinger?resource=acct:alice@${host}`, {
+ method: 'options',
+ headers: {
+ 'Access-Control-Request-Method': 'GET',
+ Origin: 'http://example.com',
+ },
+ });
+ assert.ok(preflight.ok);
+ assert.strictEqual(preflight.headers.get('Access-Control-Allow-Headers'), 'Accept');
+
+ const res = await relativeFetch(`.well-known/webfinger?resource=acct:alice@${host}`);
+ assert.ok(res.ok);
+ assert.strictEqual(res.headers.get('Access-Control-Allow-Origin'), '*');
+ assert.strictEqual(res.headers.get('Access-Control-Expose-Headers'), 'Vary');
+ assert.strictEqual(res.headers.get('Vary'), 'Accept');
+
+ const webfinger = await res.json();
+
+ assert.deepStrictEqual(webfinger, {
+ subject: `acct:alice@${host}`,
+ links: [{
+ rel: 'self',
+ type: 'application/activity+json',
+ href: `${origin}/users/${alice.id}`,
+ }, {
+ rel: 'http://webfinger.net/rel/profile-page',
+ type: 'text/html',
+ href: `${origin}/@alice`,
+ }, {
+ rel: 'http://ostatus.org/schema/1.0/subscribe',
+ template: `${origin}/authorize-follow?acct={uri}`,
+ }],
+ });
+ });
+
+ test('host-meta', async () => {
+ const res = await relativeFetch('.well-known/host-meta');
+ assert.ok(res.ok);
+ assert.strictEqual(res.headers.get('Access-Control-Allow-Origin'), '*');
+ });
+
+ test('host-meta.json', async () => {
+ const res = await relativeFetch('.well-known/host-meta.json');
+ assert.ok(res.ok);
+ assert.strictEqual(res.headers.get('Access-Control-Allow-Origin'), '*');
+
+ const hostMeta = await res.json();
+ assert.deepStrictEqual(hostMeta, {
+ links: [{
+ rel: 'lrdd',
+ type: 'application/jrd+json',
+ template: `${origin}/.well-known/webfinger?resource={uri}`,
+ }],
+ });
+ });
+
+ test('oauth-authorization-server', async () => {
+ const res = await relativeFetch('.well-known/oauth-authorization-server');
+ assert.ok(res.ok);
+ assert.strictEqual(res.headers.get('Access-Control-Allow-Origin'), '*');
+
+ const serverInfo = await res.json() as any;
+ assert.strictEqual(serverInfo.issuer, origin);
+ assert.strictEqual(serverInfo.authorization_endpoint, `${origin}/oauth/authorize`);
+ assert.strictEqual(serverInfo.token_endpoint, `${origin}/oauth/token`);
+ });
+});
diff --git a/packages/backend/test/jest.setup.ts b/packages/backend/test/jest.setup.ts
new file mode 100644
index 0000000000..cf5b9bf24d
--- /dev/null
+++ b/packages/backend/test/jest.setup.ts
@@ -0,0 +1,8 @@
+import { initTestDb, sendEnvResetRequest } from './utils.js';
+
+beforeAll(async () => {
+ await Promise.all([
+ initTestDb(false),
+ sendEnvResetRequest(),
+ ]);
+});
diff --git a/packages/backend/test/misc/mock-resolver.ts b/packages/backend/test/misc/mock-resolver.ts
index 0ff4c29bc9..c6ba188e5d 100644
--- a/packages/backend/test/misc/mock-resolver.ts
+++ b/packages/backend/test/misc/mock-resolver.ts
@@ -15,7 +15,13 @@ import type { LoggerService } from '@/core/LoggerService.js';
import type { MetaService } from '@/core/MetaService.js';
import type { UtilityService } from '@/core/UtilityService.js';
import { bindThis } from '@/decorators.js';
-import type { NoteReactionsRepository, NotesRepository, PollsRepository, UsersRepository, FollowRequestsRepository } from '@/models/_.js';
+import type {
+ FollowRequestsRepository,
+ NoteReactionsRepository,
+ NotesRepository,
+ PollsRepository,
+ UsersRepository,
+} from '@/models/_.js';
type MockResponse = {
type: string;
diff --git a/packages/backend/test/unit/AnnouncementService.ts b/packages/backend/test/unit/AnnouncementService.ts
index 99f9510907..77a8d3c587 100644
--- a/packages/backend/test/unit/AnnouncementService.ts
+++ b/packages/backend/test/unit/AnnouncementService.ts
@@ -10,7 +10,13 @@ import { ModuleMocker } from 'jest-mock';
import { Test } from '@nestjs/testing';
import { GlobalModule } from '@/GlobalModule.js';
import { AnnouncementService } from '@/core/AnnouncementService.js';
-import type { MiAnnouncement, AnnouncementsRepository, AnnouncementReadsRepository, UsersRepository, MiUser } from '@/models/_.js';
+import type {
+ AnnouncementReadsRepository,
+ AnnouncementsRepository,
+ MiAnnouncement,
+ MiUser,
+ UsersRepository,
+} from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { genAidx } from '@/misc/id/aidx.js';
import { CacheService } from '@/core/CacheService.js';
diff --git a/packages/backend/test/unit/DriveService.ts b/packages/backend/test/unit/DriveService.ts
index e50db1c01c..4a9843ffb4 100644
--- a/packages/backend/test/unit/DriveService.ts
+++ b/packages/backend/test/unit/DriveService.ts
@@ -6,7 +6,13 @@
process.env.NODE_ENV = 'test';
import { Test } from '@nestjs/testing';
-import { DeleteObjectCommandOutput, DeleteObjectCommand, NoSuchKey, InvalidObjectState, S3Client } from '@aws-sdk/client-s3';
+import {
+ DeleteObjectCommand,
+ DeleteObjectCommandOutput,
+ InvalidObjectState,
+ NoSuchKey,
+ S3Client,
+} from '@aws-sdk/client-s3';
import { mockClient } from 'aws-sdk-client-mock';
import { GlobalModule } from '@/GlobalModule.js';
import { DriveService } from '@/core/DriveService.js';
diff --git a/packages/backend/test/unit/FileInfoService.ts b/packages/backend/test/unit/FileInfoService.ts
index 9e164fbfc9..413c8402d1 100644
--- a/packages/backend/test/unit/FileInfoService.ts
+++ b/packages/backend/test/unit/FileInfoService.ts
@@ -10,7 +10,7 @@ import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
import { ModuleMocker } from 'jest-mock';
import { Test } from '@nestjs/testing';
-import { describe, beforeAll, afterAll, test } from '@jest/globals';
+import { afterAll, beforeAll, describe, test } from '@jest/globals';
import { GlobalModule } from '@/GlobalModule.js';
import { FileInfoService } from '@/core/FileInfoService.js';
//import { DI } from '@/di-symbols.js';
diff --git a/packages/backend/test/unit/MetaService.ts b/packages/backend/test/unit/MetaService.ts
index d3d84f4bd2..547f80c9a1 100644
--- a/packages/backend/test/unit/MetaService.ts
+++ b/packages/backend/test/unit/MetaService.ts
@@ -6,15 +6,13 @@
process.env.NODE_ENV = 'test';
import { jest } from '@jest/globals';
-import { ModuleMocker } from 'jest-mock';
import { Test } from '@nestjs/testing';
import { GlobalModule } from '@/GlobalModule.js';
-import type { MetasRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { MetaService } from '@/core/MetaService.js';
import { CoreModule } from '@/core/CoreModule.js';
-import type { DataSource } from 'typeorm';
import type { TestingModule } from '@nestjs/testing';
+import type { DataSource } from 'typeorm';
describe('MetaService', () => {
let app: TestingModule;
diff --git a/packages/backend/test/unit/RoleService.ts b/packages/backend/test/unit/RoleService.ts
index 7feae17051..4197f4b899 100644
--- a/packages/backend/test/unit/RoleService.ts
+++ b/packages/backend/test/unit/RoleService.ts
@@ -11,7 +11,7 @@ import { Test } from '@nestjs/testing';
import * as lolex from '@sinonjs/fake-timers';
import { GlobalModule } from '@/GlobalModule.js';
import { RoleService } from '@/core/RoleService.js';
-import type { MiRole, RolesRepository, RoleAssignmentsRepository, UsersRepository, MiUser } from '@/models/_.js';
+import type { MiRole, MiUser, RoleAssignmentsRepository, RolesRepository, UsersRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { MetaService } from '@/core/MetaService.js';
import { genAidx } from '@/misc/id/aidx.js';
@@ -19,6 +19,7 @@ import { CacheService } from '@/core/CacheService.js';
import { IdService } from '@/core/IdService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { secureRndstr } from '@/misc/secure-rndstr.js';
+import { NotificationService } from '@/core/NotificationService.js';
import { sleep } from '../utils.js';
import type { TestingModule } from '@nestjs/testing';
import type { MockFunctionMetadata } from 'jest-mock';
@@ -32,6 +33,7 @@ describe('RoleService', () => {
let rolesRepository: RolesRepository;
let roleAssignmentsRepository: RoleAssignmentsRepository;
let metaService: jest.Mocked;
+ let notificationService: jest.Mocked;
let clock: lolex.InstalledClock;
function createUser(data: Partial = {}) {
@@ -71,6 +73,16 @@ describe('RoleService', () => {
CacheService,
IdService,
GlobalEventService,
+ {
+ provide: NotificationService,
+ useFactory: () => ({
+ createNotification: jest.fn(),
+ }),
+ },
+ {
+ provide: NotificationService.name,
+ useExisting: NotificationService,
+ },
],
})
.useMocker((token) => {
@@ -93,6 +105,9 @@ describe('RoleService', () => {
roleAssignmentsRepository = app.get(DI.roleAssignmentsRepository);
metaService = app.get(MetaService) as jest.Mocked;
+ notificationService = app.get(NotificationService) as jest.Mocked;
+
+ await roleService.onModuleInit();
});
afterEach(async () => {
@@ -273,4 +288,57 @@ describe('RoleService', () => {
expect(resultAfter25hAgain.canManageCustomEmojis).toBe(true);
});
});
+
+ describe('assign', () => {
+ test('公開ロールの場合は通知される', async () => {
+ const user = await createUser();
+ const role = await createRole({
+ isPublic: true,
+ name: 'a',
+ });
+
+ await roleService.assign(user.id, role.id);
+
+ clock.uninstall();
+ await sleep(100);
+
+ const assignments = await roleAssignmentsRepository.find({
+ where: {
+ userId: user.id,
+ roleId: role.id,
+ },
+ });
+ expect(assignments).toHaveLength(1);
+
+ expect(notificationService.createNotification).toHaveBeenCalled();
+ expect(notificationService.createNotification.mock.lastCall![0]).toBe(user.id);
+ expect(notificationService.createNotification.mock.lastCall![1]).toBe('roleAssigned');
+ expect(notificationService.createNotification.mock.lastCall![2]).toEqual({
+ roleId: role.id,
+ });
+ });
+
+ test('非公開ロールの場合は通知されない', async () => {
+ const user = await createUser();
+ const role = await createRole({
+ isPublic: false,
+ name: 'a',
+ });
+
+ await roleService.assign(user.id, role.id);
+
+ clock.uninstall();
+ await sleep(100);
+
+ const assignments = await roleAssignmentsRepository.find({
+ where: {
+ userId: user.id,
+ roleId: role.id,
+ },
+ });
+ expect(assignments).toHaveLength(1);
+
+ expect(notificationService.createNotification).not.toHaveBeenCalled();
+ });
+ });
});
diff --git a/packages/backend/test/unit/S3Service.ts b/packages/backend/test/unit/S3Service.ts
index fe2cb671e0..20aa1148f7 100644
--- a/packages/backend/test/unit/S3Service.ts
+++ b/packages/backend/test/unit/S3Service.ts
@@ -6,7 +6,13 @@
process.env.NODE_ENV = 'test';
import { Test } from '@nestjs/testing';
-import { UploadPartCommand, CompleteMultipartUploadCommand, CreateMultipartUploadCommand, S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
+import {
+ CompleteMultipartUploadCommand,
+ CreateMultipartUploadCommand,
+ PutObjectCommand,
+ S3Client,
+ UploadPartCommand,
+} from '@aws-sdk/client-s3';
import { mockClient } from 'aws-sdk-client-mock';
import { GlobalModule } from '@/GlobalModule.js';
import { CoreModule } from '@/core/CoreModule.js';
diff --git a/packages/backend/test/unit/misc/id.ts b/packages/backend/test/unit/misc/id.ts
index 090429ac3c..14f6751126 100644
--- a/packages/backend/test/unit/misc/id.ts
+++ b/packages/backend/test/unit/misc/id.ts
@@ -4,13 +4,13 @@
*/
import { ulid } from 'ulid';
-import { describe, test, expect } from '@jest/globals';
+import { describe, expect, test } from '@jest/globals';
import { aidRegExp, genAid, parseAid } from '@/misc/id/aid.js';
import { aidxRegExp, genAidx, parseAidx } from '@/misc/id/aidx.js';
import { genMeid, meidRegExp, parseMeid } from '@/misc/id/meid.js';
import { genMeidg, meidgRegExp, parseMeidg } from '@/misc/id/meidg.js';
import { genObjectId, objectIdRegExp, parseObjectId } from '@/misc/id/object-id.js';
-import { ulidRegExp, parseUlid } from '@/misc/id/ulid.js';
+import { parseUlid, ulidRegExp } from '@/misc/id/ulid.js';
describe('misc:id', () => {
test('aid', () => {
diff --git a/packages/backend/test/unit/misc/others.ts b/packages/backend/test/unit/misc/others.ts
index 6182590233..4f5b2d587f 100644
--- a/packages/backend/test/unit/misc/others.ts
+++ b/packages/backend/test/unit/misc/others.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { describe, test, expect } from '@jest/globals';
+import { describe, expect, test } from '@jest/globals';
import { contentDisposition } from '@/misc/content-disposition.js';
describe('misc:content-disposition', () => {
diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts
index d03f7af9eb..4692d09f06 100644
--- a/packages/backend/test/utils.ts
+++ b/packages/backend/test/utils.ts
@@ -5,18 +5,19 @@
import * as assert from 'node:assert';
import { readFile } from 'node:fs/promises';
-import { isAbsolute, basename } from 'node:path';
+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 { DataSource } from 'typeorm';
import { JSDOM } from 'jsdom';
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
-import { entities } from '../src/postgres.js';
-import { loadConfig } from '../src/config.js';
+import { entities } from '@/postgres.js';
+import { loadConfig } from '@/config.js';
import type * as misskey from 'cherrypick-js';
-export { server as startServer } from '@/boot/common.js';
+export { server as startServer, jobQueue as startJobQueue } from '@/boot/common.js';
interface UserToken {
token: string;
@@ -25,6 +26,8 @@ interface UserToken {
const config = loadConfig();
export const port = config.port;
+export const origin = config.url;
+export const host = new URL(config.url).host;
export const cookie = (me: UserToken): string => {
return `token=${me.token};`;
@@ -65,7 +68,11 @@ export const failedApiCall = async (request: ApiRequest, assertion: {
return res.body;
};
-const request = async (path: string, params: any, me?: UserToken): Promise<{ status: number, headers: Headers, body: any }> => {
+const request = async (path: string, params: any, me?: UserToken): Promise<{
+ status: number,
+ headers: Headers,
+ body: any
+}> => {
const bodyAuth: Record = {};
const headers: Record = {
'Content-Type': 'application/json',
@@ -126,6 +133,15 @@ export const post = async (user: UserToken, params?: misskey.Endpoints['notes/cr
return res.body ? res.body.createdNote : null;
};
+export const createAppToken = async (user: UserToken, permissions: (typeof misskey.permissions)[number][]) => {
+ const res = await api('miauth/gen-token', {
+ session: randomUUID(),
+ permission: permissions,
+ }, user);
+
+ return (res.body as misskey.entities.MiauthGenTokenResponse).token;
+};
+
// 非公開ノートをAPI越しに見たときのノート NoteEntityService.ts
export const hiddenNote = (note: any): any => {
const temp = {
@@ -263,7 +279,11 @@ interface UploadOptions {
* Upload file
* @param user User
*/
-export const uploadFile = async (user?: UserToken, { path, name, blob }: UploadOptions = {}): Promise<{ status: number, headers: Headers, body: misskey.Endpoints['drive/files/create']['res'] | null }> => {
+export const uploadFile = async (user?: UserToken, { path, name, blob }: UploadOptions = {}): Promise<{
+ status: number,
+ headers: Headers,
+ body: misskey.Endpoints['drive/files/create']['res'] | null
+}> => {
const absPath = path == null
? new URL('resources/Lenna.jpg', import.meta.url)
: isAbsolute(path.toString())
@@ -414,8 +434,8 @@ export const simpleGet = async (path: string, accept = '*/*', cookie: any = unde
];
const body =
- jsonTypes.includes(res.headers.get('content-type') ?? '') ? await res.json() :
- htmlTypes.includes(res.headers.get('content-type') ?? '') ? new JSDOM(await res.text()) :
+ jsonTypes.includes(res.headers.get('content-type') ?? '') ? await res.json() :
+ htmlTypes.includes(res.headers.get('content-type') ?? '') ? new JSDOM(await res.text()) :
null;
return {
@@ -545,3 +565,34 @@ export function sleep(msec: number) {
}, msec);
});
}
+
+export async function sendEnvUpdateRequest(params: { key: string, value?: string }) {
+ const res = await fetch(
+ `http://localhost:${port + 1000}/env`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(params),
+ },
+ );
+
+ if (res.status !== 200) {
+ throw new Error('server env update failed.');
+ }
+}
+
+export async function sendEnvResetRequest() {
+ const res = await fetch(
+ `http://localhost:${port + 1000}/env-reset`,
+ {
+ method: 'POST',
+ body: JSON.stringify({}),
+ },
+ );
+
+ if (res.status !== 200) {
+ throw new Error('server env update failed.');
+ }
+}
diff --git a/packages/cherrypick-js/.swcrc b/packages/cherrypick-js/.swcrc
index d9f047b6ac..0504a2d389 100644
--- a/packages/cherrypick-js/.swcrc
+++ b/packages/cherrypick-js/.swcrc
@@ -11,7 +11,7 @@
"decoratorMetadata": true
},
"experimental": {
- "keepImportAttributes": true
+ "keepImportAssertions": true
},
"baseUrl": "src",
"paths": {
diff --git a/packages/cherrypick-js/etc/cherrypick-js.api.md b/packages/cherrypick-js/etc/cherrypick-js.api.md
index 5dd8e42281..e1d67d1611 100644
--- a/packages/cherrypick-js/etc/cherrypick-js.api.md
+++ b/packages/cherrypick-js/etc/cherrypick-js.api.md
@@ -21,6 +21,11 @@ declare namespace acct {
}
export { acct }
+// Warning: (ae-forgotten-export) The symbol "components" needs to be exported by the entry point index.d.ts
+//
+// @public (undocumented)
+type Ad = components['schemas']['Ad'];
+
// Warning: (ae-forgotten-export) The symbol "operations" needs to be exported by the entry point index.d.ts
//
// @public (undocumented)
@@ -59,15 +64,24 @@ type AdminAccountsDeleteRequest = operations['admin/accounts/delete']['requestBo
// @public (undocumented)
type AdminAccountsFindByEmailRequest = operations['admin/accounts/find-by-email']['requestBody']['content']['application/json'];
+// @public (undocumented)
+type AdminAccountsFindByEmailResponse = operations['admin/accounts/find-by-email']['responses']['200']['content']['application/json'];
+
// @public (undocumented)
type AdminAdCreateRequest = operations['admin/ad/create']['requestBody']['content']['application/json'];
+// @public (undocumented)
+type AdminAdCreateResponse = operations['admin/ad/create']['responses']['200']['content']['application/json'];
+
// @public (undocumented)
type AdminAdDeleteRequest = operations['admin/ad/delete']['requestBody']['content']['application/json'];
// @public (undocumented)
type AdminAdListRequest = operations['admin/ad/list']['requestBody']['content']['application/json'];
+// @public (undocumented)
+type AdminAdListResponse = operations['admin/ad/list']['responses']['200']['content']['application/json'];
+
// @public (undocumented)
type AdminAdUpdateRequest = operations['admin/ad/update']['requestBody']['content']['application/json'];
@@ -146,6 +160,9 @@ type AdminEmojiDeleteBulkRequest = operations['admin/emoji/delete-bulk']['reques
// @public (undocumented)
type AdminEmojiDeleteRequest = operations['admin/emoji/delete']['requestBody']['content']['application/json'];
+// @public (undocumented)
+type AdminEmojiImportZipRequest = operations['admin/emoji/import-zip']['requestBody']['content']['application/json'];
+
// @public (undocumented)
type AdminEmojiListRemoteRequest = operations['admin/emoji/list-remote']['requestBody']['content']['application/json'];
@@ -191,12 +208,18 @@ type AdminFederationRemoveAllFollowingRequest = operations['admin/federation/rem
// @public (undocumented)
type AdminFederationUpdateInstanceRequest = operations['admin/federation/update-instance']['requestBody']['content']['application/json'];
+// @public (undocumented)
+type AdminGetIndexStatsResponse = operations['admin/get-index-stats']['responses']['200']['content']['application/json'];
+
// @public (undocumented)
type AdminGetTableStatsResponse = operations['admin/get-table-stats']['responses']['200']['content']['application/json'];
// @public (undocumented)
type AdminGetUserIpsRequest = operations['admin/get-user-ips']['requestBody']['content']['application/json'];
+// @public (undocumented)
+type AdminGetUserIpsResponse = operations['admin/get-user-ips']['responses']['200']['content']['application/json'];
+
// @public (undocumented)
type AdminInviteCreateRequest = operations['admin/invite/create']['requestBody']['content']['application/json'];
@@ -281,6 +304,9 @@ type AdminRolesUpdateRequest = operations['admin/roles/update']['requestBody']['
// @public (undocumented)
type AdminRolesUsersRequest = operations['admin/roles/users']['requestBody']['content']['application/json'];
+// @public (undocumented)
+type AdminRolesUsersResponse = operations['admin/roles/users']['responses']['200']['content']['application/json'];
+
// @public (undocumented)
type AdminSendEmailRequest = operations['admin/send-email']['requestBody']['content']['application/json'];
@@ -323,8 +349,6 @@ type AdminUpdateMetaRequest = operations['admin/update-meta']['requestBody']['co
// @public (undocumented)
type AdminUpdateUserNoteRequest = operations['admin/update-user-note']['requestBody']['content']['application/json'];
-// Warning: (ae-forgotten-export) The symbol "components" needs to be exported by the entry point index.d.ts
-//
// @public (undocumented)
type Announcement = components['schemas']['Announcement'];
@@ -402,8 +426,6 @@ class APIClient {
fetch: FetchLike;
// (undocumented)
origin: string;
- // (undocumented)
- request(endpoint: E, params?: P, credential?: string | null): Promise>;
}
// @public (undocumented)
@@ -436,6 +458,9 @@ type ApShowRequest = operations['ap/show']['requestBody']['content']['applicatio
// @public (undocumented)
type ApShowResponse = operations['ap/show']['responses']['200']['content']['application/json'];
+// @public (undocumented)
+type AuthAcceptRequest = operations['auth/accept']['requestBody']['content']['application/json'];
+
// @public (undocumented)
type AuthSessionGenerateRequest = operations['auth/session/generate']['requestBody']['content']['application/json'];
@@ -1037,6 +1062,9 @@ type EmptyResponse = Record | undefined;
// @public (undocumented)
type EndpointRequest = operations['endpoint']['requestBody']['content']['application/json'];
+// @public (undocumented)
+type EndpointResponse = operations['endpoint']['responses']['200']['content']['application/json'];
+
// Warning: (ae-forgotten-export) The symbol "Overwrite" needs to be exported by the entry point index.d.ts
// Warning: (ae-forgotten-export) The symbol "Endpoints_2" needs to be exported by the entry point index.d.ts
//
@@ -1058,6 +1086,18 @@ export type Endpoints = Overwrite;
// @public (undocumented)
@@ -1077,6 +1117,12 @@ declare namespace entities {
EmojiUpdated,
EmojiDeleted,
AnnouncementCreated,
+ SignupRequest,
+ SignupResponse,
+ SignupPendingRequest,
+ SignupPendingResponse,
+ SigninRequest,
+ SigninResponse,
EmptyRequest,
EmptyResponse,
AdminMetaResponse,
@@ -1092,9 +1138,12 @@ declare namespace entities {
AdminAccountsCreateResponse,
AdminAccountsDeleteRequest,
AdminAccountsFindByEmailRequest,
+ AdminAccountsFindByEmailResponse,
AdminAdCreateRequest,
+ AdminAdCreateResponse,
AdminAdDeleteRequest,
AdminAdListRequest,
+ AdminAdListResponse,
AdminAdUpdateRequest,
AdminAnnouncementsCreateRequest,
AdminAnnouncementsCreateResponse,
@@ -1121,6 +1170,7 @@ declare namespace entities {
AdminEmojiCopyResponse,
AdminEmojiDeleteBulkRequest,
AdminEmojiDeleteRequest,
+ AdminEmojiImportZipRequest,
AdminEmojiListRemoteRequest,
AdminEmojiListRemoteResponse,
AdminEmojiListRequest,
@@ -1136,8 +1186,10 @@ declare namespace entities {
AdminFederationRefreshRemoteInstanceMetadataRequest,
AdminFederationRemoveAllFollowingRequest,
AdminFederationUpdateInstanceRequest,
+ AdminGetIndexStatsResponse,
AdminGetTableStatsResponse,
AdminGetUserIpsRequest,
+ AdminGetUserIpsResponse,
AdminInviteCreateRequest,
AdminInviteCreateResponse,
AdminInviteListRequest,
@@ -1179,6 +1231,7 @@ declare namespace entities {
AdminRolesUnassignRequest,
AdminRolesUpdateDefaultPoliciesRequest,
AdminRolesUsersRequest,
+ AdminRolesUsersResponse,
AnnouncementsRequest,
AnnouncementsResponse,
AntennasCreateRequest,
@@ -1199,6 +1252,7 @@ declare namespace entities {
AppCreateResponse,
AppShowRequest,
AppShowResponse,
+ AuthAcceptRequest,
AuthSessionGenerateRequest,
AuthSessionGenerateResponse,
AuthSessionShowRequest,
@@ -1305,6 +1359,7 @@ declare namespace entities {
EmailAddressAvailableRequest,
EmailAddressAvailableResponse,
EndpointRequest,
+ EndpointResponse,
EndpointsResponse,
FederationFollowersRequest,
FederationFollowersResponse,
@@ -1318,6 +1373,7 @@ declare namespace entities {
FederationUsersRequest,
FederationUsersResponse,
FederationStatsRequest,
+ FederationStatsResponse,
FollowingCreateRequest,
FollowingCreateResponse,
FollowingDeleteRequest,
@@ -1347,6 +1403,7 @@ declare namespace entities {
GalleryPostsUnlikeRequest,
GalleryPostsUpdateRequest,
GalleryPostsUpdateResponse,
+ GetOnlineUsersCountResponse,
GetAvatarDecorationsResponse,
HashtagsListRequest,
HashtagsListResponse,
@@ -1358,13 +1415,36 @@ declare namespace entities {
HashtagsUsersRequest,
HashtagsUsersResponse,
IResponse,
+ I2faDoneRequest,
+ I2faKeyDoneRequest,
+ I2faKeyDoneResponse,
+ I2faPasswordLessRequest,
+ I2faRegisterKeyRequest,
+ I2faRegisterKeyResponse,
+ I2faRegisterRequest,
+ I2faRegisterResponse,
+ I2faUpdateKeyRequest,
+ I2faRemoveKeyRequest,
+ I2faUnregisterRequest,
+ IAppsRequest,
+ IAppsResponse,
+ IAuthorizedAppsRequest,
+ IAuthorizedAppsResponse,
IClaimAchievementRequest,
+ IChangePasswordRequest,
+ IDeleteAccountRequest,
+ IExportFollowingRequest,
IFavoritesRequest,
IFavoritesResponse,
IGalleryLikesRequest,
IGalleryLikesResponse,
IGalleryPostsRequest,
IGalleryPostsResponse,
+ IImportBlockingRequest,
+ IImportFollowingRequest,
+ IImportMutingRequest,
+ IImportUserListsRequest,
+ IImportAntennasRequest,
INotificationsRequest,
INotificationsResponse,
INotificationsGroupedRequest,
@@ -1376,21 +1456,37 @@ declare namespace entities {
IPinRequest,
IPinResponse,
IReadAnnouncementRequest,
+ IRegenerateTokenRequest,
IRegistryGetAllRequest,
+ IRegistryGetAllResponse,
IRegistryGetDetailRequest,
+ IRegistryGetDetailResponse,
IRegistryGetRequest,
+ IRegistryGetResponse,
IRegistryKeysWithTypeRequest,
+ IRegistryKeysWithTypeResponse,
IRegistryKeysRequest,
IRegistryRemoveRequest,
+ IRegistryScopesWithDomainResponse,
IRegistrySetRequest,
+ IRevokeTokenRequest,
+ ISigninHistoryRequest,
+ ISigninHistoryResponse,
IUnpinRequest,
IUnpinResponse,
+ IUpdateEmailRequest,
+ IUpdateEmailResponse,
IUpdateRequest,
IUpdateResponse,
IUserGroupInvitesRequest,
IUserGroupInvitesResponse,
+ IMoveRequest,
+ IMoveResponse,
IWebhooksCreateRequest,
+ IWebhooksCreateResponse,
+ IWebhooksListResponse,
IWebhooksShowRequest,
+ IWebhooksShowResponse,
IWebhooksUpdateRequest,
IWebhooksDeleteRequest,
InviteCreateResponse,
@@ -1411,6 +1507,8 @@ declare namespace entities {
EmojisResponse,
EmojiRequest,
EmojiResponse,
+ MiauthGenTokenRequest,
+ MiauthGenTokenResponse,
MuteCreateRequest,
MuteDeleteRequest,
MuteListRequest,
@@ -1476,6 +1574,7 @@ declare namespace entities {
NotesUserListTimelineRequest,
NotesUserListTimelineResponse,
NotificationsCreateRequest,
+ PagePushRequest,
PagesCreateRequest,
PagesCreateResponse,
PagesDeleteRequest,
@@ -1486,8 +1585,11 @@ declare namespace entities {
PagesUnlikeRequest,
PagesUpdateRequest,
FlashCreateRequest,
+ FlashCreateResponse,
FlashDeleteRequest,
FlashFeaturedResponse,
+ FlashGenTokenRequest,
+ FlashGenTokenResponse,
FlashLikeRequest,
FlashShowRequest,
FlashShowResponse,
@@ -1504,10 +1606,12 @@ declare namespace entities {
RolesShowRequest,
RolesShowResponse,
RolesUsersRequest,
+ RolesUsersResponse,
RolesNotesRequest,
RolesNotesResponse,
RequestResetPasswordRequest,
ResetPasswordRequest,
+ ServerInfoResponse,
StatsResponse,
SwShowRegistrationRequest,
SwShowRegistrationResponse,
@@ -1517,6 +1621,7 @@ declare namespace entities {
SwRegisterResponse,
SwUnregisterRequest,
TestRequest,
+ TestResponse,
UsernameAvailableRequest,
UsernameAvailableResponse,
UsersRequest,
@@ -1566,6 +1671,7 @@ declare namespace entities {
UsersListsCreateFromPublicResponse,
UsersListsUpdateMembershipRequest,
UsersListsGetMembershipsRequest,
+ UsersListsGetMembershipsResponse,
UsersNotesRequest,
UsersNotesResponse,
UsersPagesRequest,
@@ -1588,11 +1694,14 @@ declare namespace entities {
UsersStatsRequest,
UsersStatsResponse,
UsersAchievementsRequest,
+ UsersAchievementsResponse,
UsersUpdateMemoRequest,
UsersTranslateRequest,
UsersTranslateResponse,
FetchRssRequest,
+ FetchRssResponse,
FetchExternalResourcesRequest,
+ FetchExternalResourcesResponse,
RetentionResponse,
Error_2 as Error,
UserLite,
@@ -1604,6 +1713,7 @@ declare namespace entities {
User,
UserList,
UserGroup,
+ Ad,
Announcement,
App,
MessagingMessage,
@@ -1669,6 +1779,9 @@ type FederationShowInstanceResponse = operations['federation/show-instance']['re
// @public (undocumented)
type FederationStatsRequest = operations['federation/stats']['requestBody']['content']['application/json'];
+// @public (undocumented)
+type FederationStatsResponse = operations['federation/stats']['responses']['200']['content']['application/json'];
+
// @public (undocumented)
type FederationUpdateRemoteUserRequest = operations['federation/update-remote-user']['requestBody']['content']['application/json'];
@@ -1681,6 +1794,9 @@ type FederationUsersResponse = operations['federation/users']['responses']['200'
// @public (undocumented)
type FetchExternalResourcesRequest = operations['fetch-external-resources']['requestBody']['content']['application/json'];
+// @public (undocumented)
+type FetchExternalResourcesResponse = operations['fetch-external-resources']['responses']['200']['content']['application/json'];
+
// @public (undocumented)
type FetchLike = (input: string, init?: {
method?: string;
@@ -1699,7 +1815,7 @@ type FetchLike = (input: string, init?: {
type FetchRssRequest = operations['fetch-rss']['requestBody']['content']['application/json'];
// @public (undocumented)
-export const ffVisibility: readonly ["public", "followers", "private"];
+type FetchRssResponse = operations['fetch-rss']['responses']['200']['content']['application/json'];
// @public (undocumented)
type Flash = components['schemas']['Flash'];
@@ -1707,12 +1823,21 @@ type Flash = components['schemas']['Flash'];
// @public (undocumented)
type FlashCreateRequest = operations['flash/create']['requestBody']['content']['application/json'];
+// @public (undocumented)
+type FlashCreateResponse = operations['flash/create']['responses']['200']['content']['application/json'];
+
// @public (undocumented)
type FlashDeleteRequest = operations['flash/delete']['requestBody']['content']['application/json'];
// @public (undocumented)
type FlashFeaturedResponse = operations['flash/featured']['responses']['200']['content']['application/json'];
+// @public (undocumented)
+type FlashGenTokenRequest = operations['flash/gen-token']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type FlashGenTokenResponse = operations['flash/gen-token']['responses']['200']['content']['application/json'];
+
// @public (undocumented)
type FlashLikeRequest = operations['flash/like']['requestBody']['content']['application/json'];
@@ -1740,6 +1865,9 @@ type FlashUnlikeRequest = operations['flash/unlike']['requestBody']['content']['
// @public (undocumented)
type FlashUpdateRequest = operations['flash/update']['requestBody']['content']['application/json'];
+// @public (undocumented)
+export const followersVisibilities: readonly ["public", "followers", "private"];
+
// @public (undocumented)
type Following = components['schemas']['Following'];
@@ -1788,6 +1916,9 @@ type FollowingUpdateRequest = operations['following/update']['requestBody']['con
// @public (undocumented)
type FollowingUpdateResponse = operations['following/update']['responses']['200']['content']['application/json'];
+// @public (undocumented)
+export const followingVisibilities: readonly ["public", "followers", "private"];
+
// @public (undocumented)
type GalleryFeaturedRequest = operations['gallery/featured']['requestBody']['content']['application/json'];
@@ -1836,6 +1967,9 @@ type GalleryPostsUpdateResponse = operations['gallery/posts/update']['responses'
// @public (undocumented)
type GetAvatarDecorationsResponse = operations['get-avatar-decorations']['responses']['200']['content']['application/json'];
+// @public (undocumented)
+type GetOnlineUsersCountResponse = operations['get-online-users-count']['responses']['200']['content']['application/json'];
+
// @public (undocumented)
type Hashtag = components['schemas']['Hashtag'];
@@ -1866,12 +2000,66 @@ type HashtagsUsersRequest = operations['hashtags/users']['requestBody']['content
// @public (undocumented)
type HashtagsUsersResponse = operations['hashtags/users']['responses']['200']['content']['application/json'];
+// @public (undocumented)
+type I2faDoneRequest = operations['i/2fa/done']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type I2faKeyDoneRequest = operations['i/2fa/key-done']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type I2faKeyDoneResponse = operations['i/2fa/key-done']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type I2faPasswordLessRequest = operations['i/2fa/password-less']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type I2faRegisterKeyRequest = operations['i/2fa/register-key']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type I2faRegisterKeyResponse = operations['i/2fa/register-key']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type I2faRegisterRequest = operations['i/2fa/register']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type I2faRegisterResponse = operations['i/2fa/register']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type I2faRemoveKeyRequest = operations['i/2fa/remove-key']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type I2faUnregisterRequest = operations['i/2fa/unregister']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type I2faUpdateKeyRequest = operations['i/2fa/update-key']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type IAppsRequest = operations['i/apps']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type IAppsResponse = operations['i/apps']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type IAuthorizedAppsRequest = operations['i/authorized-apps']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type IAuthorizedAppsResponse = operations['i/authorized-apps']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type IChangePasswordRequest = operations['i/change-password']['requestBody']['content']['application/json'];
+
// @public (undocumented)
type IClaimAchievementRequest = operations['i/claim-achievement']['requestBody']['content']['application/json'];
// @public (undocumented)
type ID = string;
+// @public (undocumented)
+type IDeleteAccountRequest = operations['i/delete-account']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type IExportFollowingRequest = operations['i/export-following']['requestBody']['content']['application/json'];
+
// @public (undocumented)
type IFavoritesRequest = operations['i/favorites']['requestBody']['content']['application/json'];
@@ -1890,6 +2078,27 @@ type IGalleryPostsRequest = operations['i/gallery/posts']['requestBody']['conten
// @public (undocumented)
type IGalleryPostsResponse = operations['i/gallery/posts']['responses']['200']['content']['application/json'];
+// @public (undocumented)
+type IImportAntennasRequest = operations['i/import-antennas']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type IImportBlockingRequest = operations['i/import-blocking']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type IImportFollowingRequest = operations['i/import-following']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type IImportMutingRequest = operations['i/import-muting']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type IImportUserListsRequest = operations['i/import-user-lists']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type IMoveRequest = operations['i/move']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type IMoveResponse = operations['i/move']['responses']['200']['content']['application/json'];
+
// @public (undocumented)
type INotificationsGroupedRequest = operations['i/notifications-grouped']['requestBody']['content']['application/json'];
@@ -1941,39 +2150,72 @@ type IPinResponse = operations['i/pin']['responses']['200']['content']['applicat
// @public (undocumented)
type IReadAnnouncementRequest = operations['i/read-announcement']['requestBody']['content']['application/json'];
+// @public (undocumented)
+type IRegenerateTokenRequest = operations['i/regenerate-token']['requestBody']['content']['application/json'];
+
// @public (undocumented)
type IRegistryGetAllRequest = operations['i/registry/get-all']['requestBody']['content']['application/json'];
+// @public (undocumented)
+type IRegistryGetAllResponse = operations['i/registry/get-all']['responses']['200']['content']['application/json'];
+
// @public (undocumented)
type IRegistryGetDetailRequest = operations['i/registry/get-detail']['requestBody']['content']['application/json'];
+// @public (undocumented)
+type IRegistryGetDetailResponse = operations['i/registry/get-detail']['responses']['200']['content']['application/json'];
+
// @public (undocumented)
type IRegistryGetRequest = operations['i/registry/get']['requestBody']['content']['application/json'];
+// @public (undocumented)
+type IRegistryGetResponse = operations['i/registry/get']['responses']['200']['content']['application/json'];
+
// @public (undocumented)
type IRegistryKeysRequest = operations['i/registry/keys']['requestBody']['content']['application/json'];
// @public (undocumented)
type IRegistryKeysWithTypeRequest = operations['i/registry/keys-with-type']['requestBody']['content']['application/json'];
+// @public (undocumented)
+type IRegistryKeysWithTypeResponse = operations['i/registry/keys-with-type']['responses']['200']['content']['application/json'];
+
// @public (undocumented)
type IRegistryRemoveRequest = operations['i/registry/remove']['requestBody']['content']['application/json'];
+// @public (undocumented)
+type IRegistryScopesWithDomainResponse = operations['i/registry/scopes-with-domain']['responses']['200']['content']['application/json'];
+
// @public (undocumented)
type IRegistrySetRequest = operations['i/registry/set']['requestBody']['content']['application/json'];
// @public (undocumented)
type IResponse = operations['i']['responses']['200']['content']['application/json'];
+// @public (undocumented)
+type IRevokeTokenRequest = operations['i/revoke-token']['requestBody']['content']['application/json'];
+
// @public (undocumented)
function isAPIError(reason: any): reason is APIError;
+// @public (undocumented)
+type ISigninHistoryRequest = operations['i/signin-history']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type ISigninHistoryResponse = operations['i/signin-history']['responses']['200']['content']['application/json'];
+
// @public (undocumented)
type IUnpinRequest = operations['i/unpin']['requestBody']['content']['application/json'];
// @public (undocumented)
type IUnpinResponse = operations['i/unpin']['responses']['200']['content']['application/json'];
+// @public (undocumented)
+type IUpdateEmailRequest = operations['i/update-email']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type IUpdateEmailResponse = operations['i/update-email']['responses']['200']['content']['application/json'];
+
// @public (undocumented)
type IUpdateRequest = operations['i/update']['requestBody']['content']['application/json'];
@@ -1989,12 +2231,21 @@ type IUserGroupInvitesResponse = operations['i/user-group-invites']['responses']
// @public (undocumented)
type IWebhooksCreateRequest = operations['i/webhooks/create']['requestBody']['content']['application/json'];
+// @public (undocumented)
+type IWebhooksCreateResponse = operations['i/webhooks/create']['responses']['200']['content']['application/json'];
+
// @public (undocumented)
type IWebhooksDeleteRequest = operations['i/webhooks/delete']['requestBody']['content']['application/json'];
+// @public (undocumented)
+type IWebhooksListResponse = operations['i/webhooks/list']['responses']['200']['content']['application/json'];
+
// @public (undocumented)
type IWebhooksShowRequest = operations['i/webhooks/show']['requestBody']['content']['application/json'];
+// @public (undocumented)
+type IWebhooksShowResponse = operations['i/webhooks/show']['responses']['200']['content']['application/json'];
+
// @public (undocumented)
type IWebhooksUpdateRequest = operations['i/webhooks/update']['requestBody']['content']['application/json'];
@@ -2037,6 +2288,12 @@ type MetaRequest = operations['meta']['requestBody']['content']['application/jso
// @public (undocumented)
type MetaResponse = operations['meta']['responses']['200']['content']['application/json'];
+// @public (undocumented)
+type MiauthGenTokenRequest = operations['miauth/gen-token']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type MiauthGenTokenResponse = operations['miauth/gen-token']['responses']['200']['content']['application/json'];
+
// @public (undocumented)
type ModerationLog = {
id: ID;
@@ -2364,7 +2621,7 @@ type Notification_2 = components['schemas']['Notification'];
type NotificationsCreateRequest = operations['notifications/create']['requestBody']['content']['application/json'];
// @public (undocumented)
-export const notificationTypes: readonly ["note", "follow", "mention", "reply", "renote", "quote", "reaction", "pollVote", "pollEnded", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app", "achievementEarned"];
+export const notificationTypes: readonly ["note", "follow", "mention", "reply", "renote", "quote", "reaction", "pollVote", "pollEnded", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app", "roleAssigned", "achievementEarned"];
// @public (undocumented)
type Page = components['schemas']['Page'];
@@ -2378,6 +2635,9 @@ type PageEvent = {
user: User;
};
+// @public (undocumented)
+type PagePushRequest = operations['page-push']['requestBody']['content']['application/json'];
+
// @public (undocumented)
type PagesCreateRequest = operations['pages/create']['requestBody']['content']['application/json'];
@@ -2409,7 +2669,7 @@ type PagesUpdateRequest = operations['pages/update']['requestBody']['content']['
function parse(acct: string): Acct;
// @public (undocumented)
-export const permissions: string[];
+export const permissions: readonly ["read:account", "write:account", "read:blocks", "write:blocks", "read:drive", "write:drive", "read:favorites", "write:favorites", "read:following", "write:following", "read:messaging", "write:messaging", "read:mutes", "write:mutes", "write:notes", "read:notifications", "write:notifications", "read:reactions", "write:reactions", "write:votes", "read:pages", "write:pages", "write:page-likes", "read:page-likes", "read:user-groups", "write:user-groups", "read:channels", "write:channels", "read:gallery", "write:gallery", "read:gallery-likes", "write:gallery-likes", "read:flash", "write:flash", "read:flash-likes", "write:flash-likes", "read:admin:abuse-user-reports", "write:admin:delete-account", "write:admin:delete-all-files-of-a-user", "read:admin:index-stats", "read:admin:table-stats", "read:admin:user-ips", "read:admin:meta", "write:admin:reset-password", "write:admin:resolve-abuse-user-report", "write:admin:send-email", "read:admin:server-info", "read:admin:show-moderation-log", "read:admin:show-user", "read:admin:show-users", "write:admin:suspend-user", "write:admin:unset-user-avatar", "write:admin:unset-user-banner", "write:admin:unsuspend-user", "write:admin:meta", "write:admin:user-note", "write:admin:roles", "read:admin:roles", "write:admin:relays", "read:admin:relays", "write:admin:invite-codes", "read:admin:invite-codes", "write:admin:announcements", "read:admin:announcements", "write:admin:avatar-decorations", "read:admin:avatar-decorations", "write:admin:federation", "write:admin:account", "read:admin:account", "write:admin:emoji", "read:admin:emoji", "write:admin:queue", "read:admin:queue", "write:admin:promo", "write:admin:drive", "read:admin:drive", "write:admin:ad", "read:admin:ad", "write:invite-codes", "read:invite-codes", "write:clip-favorite", "read:clip-favorite", "read:federation", "write:report-abuse"];
// @public (undocumented)
type PingResponse = operations['ping']['responses']['200']['content']['application/json'];
@@ -2440,7 +2700,7 @@ type QueueStats = {
};
// @public (undocumented)
-type QueueStatsLog = string[];
+type QueueStatsLog = QueueStats[];
// @public (undocumented)
type RenoteMuteCreateRequest = operations['renote-mute/create']['requestBody']['content']['application/json'];
@@ -2490,6 +2750,12 @@ type RolesShowResponse = operations['roles/show']['responses']['200']['content']
// @public (undocumented)
type RolesUsersRequest = operations['roles/users']['requestBody']['content']['application/json'];
+// @public (undocumented)
+type RolesUsersResponse = operations['roles/users']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type ServerInfoResponse = operations['server-info']['responses']['200']['content']['application/json'];
+
// @public (undocumented)
type ServerStats = {
cpu: number;
@@ -2508,11 +2774,52 @@ type ServerStats = {
};
// @public (undocumented)
-type ServerStatsLog = string[];
+type ServerStatsLog = ServerStats[];
// @public (undocumented)
type Signin = components['schemas']['Signin'];
+// @public (undocumented)
+type SigninRequest = {
+ username: string;
+ password: string;
+ token?: string;
+};
+
+// @public (undocumented)
+type SigninResponse = {
+ id: User['id'];
+ i: string;
+};
+
+// @public (undocumented)
+type SignupPendingRequest = {
+ code: string;
+};
+
+// @public (undocumented)
+type SignupPendingResponse = {
+ id: User['id'];
+ i: string;
+};
+
+// @public (undocumented)
+type SignupRequest = {
+ username: string;
+ password: string;
+ host?: string;
+ invitationCode?: string;
+ emailAddress?: string;
+ 'hcaptcha-response'?: string | null;
+ 'g-recaptcha-response'?: string | null;
+ 'turnstile-response'?: string | null;
+};
+
+// @public (undocumented)
+type SignupResponse = MeDetailed & {
+ token: string;
+};
+
// @public (undocumented)
type StatsResponse = operations['stats']['responses']['200']['content']['application/json'];
@@ -2586,6 +2893,9 @@ type SwUpdateRegistrationResponse = operations['sw/update-registration']['respon
// @public (undocumented)
type TestRequest = operations['test']['requestBody']['content']['application/json'];
+// @public (undocumented)
+type TestResponse = operations['test']['responses']['200']['content']['application/json'];
+
// @public (undocumented)
function toString_2(acct: Acct): string;
@@ -2619,6 +2929,9 @@ type UsernameAvailableResponse = operations['username/available']['responses']['
// @public (undocumented)
type UsersAchievementsRequest = operations['users/achievements']['requestBody']['content']['application/json'];
+// @public (undocumented)
+type UsersAchievementsResponse = operations['users/achievements']['responses']['200']['content']['application/json'];
+
// @public (undocumented)
type UsersClipsRequest = operations['users/clips']['requestBody']['content']['application/json'];
@@ -2730,6 +3043,9 @@ type UsersListsFavoriteRequest = operations['users/lists/favorite']['requestBody
// @public (undocumented)
type UsersListsGetMembershipsRequest = operations['users/lists/get-memberships']['requestBody']['content']['application/json'];
+// @public (undocumented)
+type UsersListsGetMembershipsResponse = operations['users/lists/get-memberships']['responses']['200']['content']['application/json'];
+
// @public (undocumented)
type UsersListsListRequest = operations['users/lists/list']['requestBody']['content']['application/json'];
diff --git a/packages/cherrypick-js/generator/package.json b/packages/cherrypick-js/generator/package.json
index c7a62d389c..bda56d0913 100644
--- a/packages/cherrypick-js/generator/package.json
+++ b/packages/cherrypick-js/generator/package.json
@@ -8,15 +8,16 @@
},
"devDependencies": {
"@apidevtools/swagger-parser": "10.1.0",
+ "@misskey-dev/eslint-plugin": "^1.0.0",
"@types/node": "20.9.1",
"@typescript-eslint/eslint-plugin": "6.11.0",
"@typescript-eslint/parser": "6.11.0",
"eslint": "8.53.0",
- "typescript": "5.3.2",
- "tsx": "4.4.0",
- "ts-case-convert": "2.0.2",
"openapi-types": "12.1.3",
- "openapi-typescript": "6.7.1"
+ "openapi-typescript": "6.7.1",
+ "ts-case-convert": "2.0.2",
+ "tsx": "4.4.0",
+ "typescript": "5.3.3"
},
"files": [
"built"
diff --git a/packages/cherrypick-js/generator/src/generator.ts b/packages/cherrypick-js/generator/src/generator.ts
index 630c1e7e31..34c26f574b 100644
--- a/packages/cherrypick-js/generator/src/generator.ts
+++ b/packages/cherrypick-js/generator/src/generator.ts
@@ -160,6 +160,68 @@ async function generateEndpoints(
await writeFile(endpointOutputPath, endpointOutputLine.join('\n'));
}
+async function generateApiClientJSDoc(
+ openApiDocs: OpenAPIV3.Document,
+ apiClientFileName: string,
+ endpointsFileName: string,
+ warningsOutputPath: string,
+) {
+ const endpoints: { operationId: string; description: string; }[] = [];
+
+ // cherrypick-jsはPOST固定で送っているので、こちらも決め打ちする。別メソッドに対応することがあればこちらも直す必要あり
+ const paths = openApiDocs.paths;
+ const postPathItems = Object.keys(paths)
+ .map(it => paths[it]?.post)
+ .filter(filterUndefined);
+
+ for (const operation of postPathItems) {
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ const operationId = operation.operationId!;
+
+ if (operation.description) {
+ endpoints.push({
+ operationId: operationId,
+ description: operation.description,
+ });
+ }
+ }
+
+ const endpointOutputLine: string[] = [];
+
+ endpointOutputLine.push(generateVersionHeaderComment(openApiDocs));
+ endpointOutputLine.push('');
+
+ endpointOutputLine.push(`import type { SwitchCaseResponseType } from '${toImportPath(apiClientFileName)}';`);
+ endpointOutputLine.push(`import type { Endpoints } from '${toImportPath(endpointsFileName)}';`);
+ endpointOutputLine.push('');
+
+ endpointOutputLine.push(`declare module '${toImportPath(apiClientFileName)}' {`);
+ endpointOutputLine.push(' export interface APIClient {');
+ for (let i = 0; i < endpoints.length; i++) {
+ const endpoint = endpoints[i];
+
+ endpointOutputLine.push(
+ ' /**',
+ ` * ${endpoint.description.split('\n').join('\n * ')}`,
+ ' */',
+ ` request(`,
+ ' endpoint: E,',
+ ' params: P,',
+ ' credential?: string | null,',
+ ' ): Promise>;',
+ );
+
+ if (i < endpoints.length - 1) {
+ endpointOutputLine.push('\n');
+ }
+ }
+ endpointOutputLine.push(' }');
+ endpointOutputLine.push('}');
+ endpointOutputLine.push('');
+
+ await writeFile(warningsOutputPath, endpointOutputLine.join('\n'));
+}
+
function isRequestBodyObject(value: unknown): value is OpenAPIV3.RequestBodyObject {
if (!value) {
return false;
@@ -280,6 +342,9 @@ async function main() {
const entitiesFileName = `${generatePath}/entities.ts`;
const endpointFileName = `${generatePath}/endpoint.ts`;
await generateEndpoints(openApiDocs, typeFileName, entitiesFileName, endpointFileName);
+
+ const apiClientWarningFileName = `${generatePath}/apiClientJSDoc.ts`;
+ await generateApiClientJSDoc(openApiDocs, '../api.ts', endpointFileName, apiClientWarningFileName);
}
main();
diff --git a/packages/cherrypick-js/package.json b/packages/cherrypick-js/package.json
index d4690151ee..58ab00e0f3 100644
--- a/packages/cherrypick-js/package.json
+++ b/packages/cherrypick-js/package.json
@@ -6,6 +6,7 @@
"types": "./built/index.d.ts",
"scripts": {
"build": "tsc",
+ "watch": "nodemon -w src -e ts,js,cjs,mjs,json --exec \"pnpm run build\"",
"tsd": "tsd",
"api": "pnpm api-extractor run --local --verbose",
"api-prod": "pnpm api-extractor run --verbose",
@@ -21,20 +22,22 @@
"url": "git+https://github.com/misskey-dev/misskey.js.git"
},
"devDependencies": {
- "@microsoft/api-extractor": "7.38.3",
+ "@microsoft/api-extractor": "7.38.5",
+ "@misskey-dev/eslint-plugin": "^1.0.0",
"@swc/jest": "0.2.29",
- "@types/jest": "29.5.10",
- "@types/node": "20.10.3",
- "@typescript-eslint/eslint-plugin": "6.13.1",
- "@typescript-eslint/parser": "6.13.1",
- "eslint": "8.55.0",
+ "@types/jest": "29.5.11",
+ "@types/node": "20.10.5",
+ "@typescript-eslint/eslint-plugin": "6.14.0",
+ "@typescript-eslint/parser": "6.14.0",
+ "eslint": "8.56.0",
"jest": "29.7.0",
"jest-fetch-mock": "3.0.3",
"jest-websocket-mock": "2.5.0",
"mock-socket": "9.3.1",
- "tsd": "0.29.0",
- "typescript": "5.3.2",
- "ncp": "2.0.0"
+ "ncp": "2.0.0",
+ "nodemon": "3.0.2",
+ "tsd": "0.30.0",
+ "typescript": "5.3.3"
},
"files": [
"built"
diff --git a/packages/cherrypick-js/src/api.ts b/packages/cherrypick-js/src/api.ts
index c2fa4f1790..0d10faaada 100644
--- a/packages/cherrypick-js/src/api.ts
+++ b/packages/cherrypick-js/src/api.ts
@@ -1,3 +1,5 @@
+import './autogen/apiClientJSDoc';
+
import { SwitchCaseResponseType } from './api.types';
import type { Endpoints } from './api.types';
diff --git a/packages/cherrypick-js/src/api.types.ts b/packages/cherrypick-js/src/api.types.ts
index d97646b7cc..75ab7d91b1 100644
--- a/packages/cherrypick-js/src/api.types.ts
+++ b/packages/cherrypick-js/src/api.types.ts
@@ -1,6 +1,14 @@
import { Endpoints as Gen } from './autogen/endpoint';
import { UserDetailed } from './autogen/models';
import { UsersShowRequest } from './autogen/entities';
+import {
+ SigninRequest,
+ SigninResponse,
+ SignupPendingRequest,
+ SignupPendingResponse,
+ SignupRequest,
+ SignupResponse,
+} from './entities';
type Overwrite = Omit<
T,
@@ -55,6 +63,21 @@ export type Endpoints = Overwrite<
$default: UserDetailed;
};
};
- }
+ },
+ // api.jsonには載せないものなのでここで定義
+ 'signup': {
+ req: SignupRequest;
+ res: SignupResponse;
+ },
+ // api.jsonには載せないものなのでここで定義
+ 'signup-pending': {
+ req: SignupPendingRequest;
+ res: SignupPendingResponse;
+ },
+ // api.jsonには載せないものなのでここで定義
+ 'signin': {
+ req: SigninRequest;
+ res: SigninResponse;
+ },
}
>
diff --git a/packages/cherrypick-js/src/autogen/apiClientJSDoc.ts b/packages/cherrypick-js/src/autogen/apiClientJSDoc.ts
new file mode 100644
index 0000000000..6d589febbf
--- /dev/null
+++ b/packages/cherrypick-js/src/autogen/apiClientJSDoc.ts
@@ -0,0 +1,4332 @@
+/*
+ * version: 4.6.0
+ * basedMisskeyVersion: 2023.12.2
+ * generatedAt: 2024-01-10T07:26:44.256Z
+ */
+
+import type { SwitchCaseResponseType } from '../api.js';
+import type { Endpoints } from './endpoint.js';
+
+declare module '../api.js' {
+ export interface APIClient {
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:admin:meta*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *No*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:admin:abuse-user-reports*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *No*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:admin:account*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:admin:account*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:admin:ad*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:admin:ad*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:admin:ad*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:admin:ad*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:admin:announcements*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:admin:announcements*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:admin:announcements*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:admin:announcements*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:admin:avatar-decorations*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:admin:avatar-decorations*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:admin:avatar-decorations*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:admin:avatar-decorations*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:admin:delete-all-files-of-a-user*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:admin:unset-user-avatar*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:admin:unset-user-banner*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:admin:drive*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:admin:drive*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:admin:drive*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *read:admin:drive*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:admin:emoji*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:admin:emoji*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:admin:emoji*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:admin:emoji*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:admin:emoji*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Credential required**: *Yes* / **Permission**: *write:admin:emoji*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise>;
+
+ /**
+ * No description provided.
+ *
+ * **Internal Endpoint**: This endpoint is an API for the cherrypick mainframe and is not intended for use by third parties.
+ * **Credential required**: *Yes*
+ */
+ request(
+ endpoint: E,
+ params: P,
+ credential?: string | null,
+ ): Promise