fix(backend): better notes/translate error response (#13631)

* fix(backend): better `notes/translate` error response

* Update CHANGELOG.md

* test(backend): perform administrative operations as `root`

---------

Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
This commit is contained in:
zyoshoka 2024-03-30 13:51:53 +09:00 committed by GitHub
parent f3500ffda9
commit b35ae97ba7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 97 additions and 28 deletions

View File

@ -35,6 +35,7 @@
- Enhance: misskey-dev/summaly@5.1.0の取り込み(プレビュー生成処理の効率化) - Enhance: misskey-dev/summaly@5.1.0の取り込み(プレビュー生成処理の効率化)
- Fix: フォローリクエストを作成する際に既存のものは削除するように - Fix: フォローリクエストを作成する際に既存のものは削除するように
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/440) (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/440)
- Fix: エンドポイント`notes/translate`のエラーを改善
- Fix: CleanRemoteFilesProcessorService report progress from 100% (#13632) - Fix: CleanRemoteFilesProcessorService report progress from 100% (#13632)
## 2024.3.1 ## 2024.3.1

View File

@ -21,7 +21,7 @@ export const meta = {
res: { res: {
type: 'object', type: 'object',
optional: false, nullable: false, optional: true, nullable: false,
properties: { properties: {
sourceLang: { type: 'string' }, sourceLang: { type: 'string' },
text: { type: 'string' }, text: { type: 'string' },
@ -39,6 +39,11 @@ export const meta = {
code: 'NO_SUCH_NOTE', code: 'NO_SUCH_NOTE',
id: 'bea9b03f-36e0-49c5-a4db-627a029f8971', id: 'bea9b03f-36e0-49c5-a4db-627a029f8971',
}, },
cannotTranslateInvisibleNote: {
message: 'Cannot translate invisible note.',
code: 'CANNOT_TRANSLATE_INVISIBLE_NOTE',
id: 'ea29f2ca-c368-43b3-aaf1-5ac3e74bbe5d',
},
}, },
} as const; } as const;
@ -72,17 +77,17 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}); });
if (!(await this.noteEntityService.isVisibleForMe(note, me.id))) { if (!(await this.noteEntityService.isVisibleForMe(note, me.id))) {
return 204; // TODO: 良い感じのエラー返す throw new ApiError(meta.errors.cannotTranslateInvisibleNote);
} }
if (note.text == null) { if (note.text == null) {
return 204; return;
} }
const instance = await this.metaService.fetch(); const instance = await this.metaService.fetch();
if (instance.deeplAuthKey == null) { if (instance.deeplAuthKey == null) {
return 204; // TODO: 良い感じのエラー返す throw new ApiError(meta.errors.unavailable);
} }
let targetLang = ps.targetLang; let targetLang = ps.targetLang;

View File

@ -8,12 +8,13 @@ process.env.NODE_ENV = 'test';
import * as assert from 'assert'; import * as assert from 'assert';
import { MiNote } from '@/models/Note.js'; import { MiNote } from '@/models/Note.js';
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
import { api, initTestDb, post, signup, uploadFile, uploadUrl } from '../utils.js'; import { api, initTestDb, post, role, signup, uploadFile, uploadUrl } from '../utils.js';
import type * as misskey from 'misskey-js'; import type * as misskey from 'misskey-js';
describe('Note', () => { describe('Note', () => {
let Notes: any; let Notes: any;
let root: misskey.entities.SignupResponse;
let alice: misskey.entities.SignupResponse; let alice: misskey.entities.SignupResponse;
let bob: misskey.entities.SignupResponse; let bob: misskey.entities.SignupResponse;
let tom: misskey.entities.SignupResponse; let tom: misskey.entities.SignupResponse;
@ -21,6 +22,7 @@ describe('Note', () => {
beforeAll(async () => { beforeAll(async () => {
const connection = await initTestDb(true); const connection = await initTestDb(true);
Notes = connection.getRepository(MiNote); Notes = connection.getRepository(MiNote);
root = await signup({ username: 'root' });
alice = await signup({ username: 'alice' }); alice = await signup({ username: 'alice' });
bob = await signup({ username: 'bob' }); bob = await signup({ username: 'bob' });
tom = await signup({ username: 'tom', host: 'example.com' }); tom = await signup({ username: 'tom', host: 'example.com' });
@ -473,14 +475,14 @@ describe('Note', () => {
value: true, value: true,
}, },
} as any, } as any,
}, alice); }, root);
assert.strictEqual(res.status, 200); assert.strictEqual(res.status, 200);
const assign = await api('admin/roles/assign', { const assign = await api('admin/roles/assign', {
userId: alice.id, userId: alice.id,
roleId: res.body.id, roleId: res.body.id,
}, alice); }, root);
assert.strictEqual(assign.status, 204); assert.strictEqual(assign.status, 204);
assert.strictEqual(file.body!.isSensitive, false); assert.strictEqual(file.body!.isSensitive, false);
@ -508,11 +510,11 @@ describe('Note', () => {
await api('admin/roles/unassign', { await api('admin/roles/unassign', {
userId: alice.id, userId: alice.id,
roleId: res.body.id, roleId: res.body.id,
}); }, root);
await api('admin/roles/delete', { await api('admin/roles/delete', {
roleId: res.body.id, roleId: res.body.id,
}, alice); }, root);
}); });
}); });
@ -644,7 +646,7 @@ describe('Note', () => {
sensitiveWords: [ sensitiveWords: [
'test', 'test',
], ],
}, alice); }, root);
assert.strictEqual(sensitive.status, 204); assert.strictEqual(sensitive.status, 204);
@ -663,7 +665,7 @@ describe('Note', () => {
sensitiveWords: [ sensitiveWords: [
'/Test/i', '/Test/i',
], ],
}, alice); }, root);
assert.strictEqual(sensitive.status, 204); assert.strictEqual(sensitive.status, 204);
@ -680,7 +682,7 @@ describe('Note', () => {
sensitiveWords: [ sensitiveWords: [
'Test hoge', 'Test hoge',
], ],
}, alice); }, root);
assert.strictEqual(sensitive.status, 204); assert.strictEqual(sensitive.status, 204);
@ -697,7 +699,7 @@ describe('Note', () => {
prohibitedWords: [ prohibitedWords: [
'test', 'test',
], ],
}, alice); }, root);
assert.strictEqual(prohibited.status, 204); assert.strictEqual(prohibited.status, 204);
@ -716,7 +718,7 @@ describe('Note', () => {
prohibitedWords: [ prohibitedWords: [
'/Test/i', '/Test/i',
], ],
}, alice); }, root);
assert.strictEqual(prohibited.status, 204); assert.strictEqual(prohibited.status, 204);
@ -733,7 +735,7 @@ describe('Note', () => {
prohibitedWords: [ prohibitedWords: [
'Test hoge', 'Test hoge',
], ],
}, alice); }, root);
assert.strictEqual(prohibited.status, 204); assert.strictEqual(prohibited.status, 204);
@ -750,7 +752,7 @@ describe('Note', () => {
prohibitedWords: [ prohibitedWords: [
'test', 'test',
], ],
}, alice); }, root);
assert.strictEqual(prohibited.status, 204); assert.strictEqual(prohibited.status, 204);
@ -785,7 +787,7 @@ describe('Note', () => {
value: 0, value: 0,
}, },
} as any, } as any,
}, alice); }, root);
assert.strictEqual(res.status, 200); assert.strictEqual(res.status, 200);
@ -794,7 +796,7 @@ describe('Note', () => {
const assign = await api('admin/roles/assign', { const assign = await api('admin/roles/assign', {
userId: alice.id, userId: alice.id,
roleId: res.body.id, roleId: res.body.id,
}, alice); }, root);
assert.strictEqual(assign.status, 204); assert.strictEqual(assign.status, 204);
@ -810,11 +812,11 @@ describe('Note', () => {
await api('admin/roles/unassign', { await api('admin/roles/unassign', {
userId: alice.id, userId: alice.id,
roleId: res.body.id, roleId: res.body.id,
}); }, root);
await api('admin/roles/delete', { await api('admin/roles/delete', {
roleId: res.body.id, roleId: res.body.id,
}, alice); }, root);
}); });
test('ダイレクト投稿もエラーになる', async () => { test('ダイレクト投稿もエラーになる', async () => {
@ -839,7 +841,7 @@ describe('Note', () => {
value: 0, value: 0,
}, },
} as any, } as any,
}, alice); }, root);
assert.strictEqual(res.status, 200); assert.strictEqual(res.status, 200);
@ -848,7 +850,7 @@ describe('Note', () => {
const assign = await api('admin/roles/assign', { const assign = await api('admin/roles/assign', {
userId: alice.id, userId: alice.id,
roleId: res.body.id, roleId: res.body.id,
}, alice); }, root);
assert.strictEqual(assign.status, 204); assert.strictEqual(assign.status, 204);
@ -866,11 +868,11 @@ describe('Note', () => {
await api('admin/roles/unassign', { await api('admin/roles/unassign', {
userId: alice.id, userId: alice.id,
roleId: res.body.id, roleId: res.body.id,
}); }, root);
await api('admin/roles/delete', { await api('admin/roles/delete', {
roleId: res.body.id, roleId: res.body.id,
}, alice); }, root);
}); });
test('ダイレクトの宛先とメンションが同じ場合は重複してカウントしない', async () => { test('ダイレクトの宛先とメンションが同じ場合は重複してカウントしない', async () => {
@ -895,7 +897,7 @@ describe('Note', () => {
value: 1, value: 1,
}, },
} as any, } as any,
}, alice); }, root);
assert.strictEqual(res.status, 200); assert.strictEqual(res.status, 200);
@ -904,7 +906,7 @@ describe('Note', () => {
const assign = await api('admin/roles/assign', { const assign = await api('admin/roles/assign', {
userId: alice.id, userId: alice.id,
roleId: res.body.id, roleId: res.body.id,
}, alice); }, root);
assert.strictEqual(assign.status, 204); assert.strictEqual(assign.status, 204);
@ -921,11 +923,11 @@ describe('Note', () => {
await api('admin/roles/unassign', { await api('admin/roles/unassign', {
userId: alice.id, userId: alice.id,
roleId: res.body.id, roleId: res.body.id,
}); }, root);
await api('admin/roles/delete', { await api('admin/roles/delete', {
roleId: res.body.id, roleId: res.body.id,
}, alice); }, root);
}); });
}); });
@ -960,4 +962,61 @@ describe('Note', () => {
assert.strictEqual(mainNote.repliesCount, 0); assert.strictEqual(mainNote.repliesCount, 0);
}); });
}); });
describe('notes/translate', () => {
describe('翻訳機能の利用が許可されていない場合', () => {
let cannotTranslateRole: misskey.entities.Role;
beforeAll(async () => {
cannotTranslateRole = await role(root, {}, { canUseTranslator: false });
await api('admin/roles/assign', { roleId: cannotTranslateRole.id, userId: alice.id }, root);
});
test('翻訳機能の利用が許可されていない場合翻訳できない', async () => {
const aliceNote = await post(alice, { text: 'Hello' });
const res = await api('notes/translate', {
noteId: aliceNote.id,
targetLang: 'ja',
}, alice);
assert.strictEqual(res.status, 400);
assert.strictEqual(res.body.error.code, 'UNAVAILABLE');
});
afterAll(async () => {
await api('admin/roles/unassign', { roleId: cannotTranslateRole.id, userId: alice.id }, root);
});
});
test('存在しないノートは翻訳できない', async () => {
const res = await api('notes/translate', { noteId: 'foo', targetLang: 'ja' }, alice);
assert.strictEqual(res.status, 400);
assert.strictEqual(res.body.error.code, 'NO_SUCH_NOTE');
});
test('不可視なノートは翻訳できない', async () => {
const aliceNote = await post(alice, { visibility: 'followers', text: 'Hello' });
const bobTranslateAttempt = await api('notes/translate', { noteId: aliceNote.id, targetLang: 'ja' }, bob);
assert.strictEqual(bobTranslateAttempt.status, 400);
assert.strictEqual(bobTranslateAttempt.body.error.code, 'CANNOT_TRANSLATE_INVISIBLE_NOTE');
});
test('text: null なノートを翻訳すると空のレスポンスが返ってくる', async () => {
const aliceNote = await post(alice, { text: null, poll: { choices: ['kinoko', 'takenoko'] } });
const res = await api('notes/translate', { noteId: aliceNote.id, targetLang: 'ja' }, alice);
assert.strictEqual(res.status, 204);
});
test('サーバーに DeepL 認証キーが登録されていない場合翻訳できない', async () => {
const aliceNote = await post(alice, { text: 'Hello' });
const res = await api('notes/translate', { noteId: aliceNote.id, targetLang: 'ja' }, alice);
// NOTE: デフォルトでは登録されていないので落ちる
assert.strictEqual(res.status, 400);
assert.strictEqual(res.body.error.code, 'UNAVAILABLE');
});
});
}); });

View File

@ -21864,6 +21864,10 @@ export type operations = {
}; };
}; };
}; };
/** @description OK (without any results) */
204: {
content: never;
};
/** @description Client error */ /** @description Client error */
400: { 400: {
content: { content: {