fix(backend): clips/updateのdescriptionで空文字を許容するように (#15429)

* fix(backend): clips/updateのdescriptionで空文字を許容するように

* Update Changelog

* fix: createの際も空文字を許容するように

* fix test

* fix test

---------

Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
This commit is contained in:
かっこかり 2025-02-26 10:27:38 +09:00 committed by GitHub
parent 7f31fd24b1
commit b5799351d0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 44 additions and 5 deletions

View File

@ -27,6 +27,7 @@
- Fix: pgroongaでの検索時にはじめのキーワードのみが検索に使用される問題を修正 - Fix: pgroongaでの検索時にはじめのキーワードのみが検索に使用される問題を修正
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/886) (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/886)
- Fix: メールアドレスの形式が正しくなければ以降の処理を行わないように - Fix: メールアドレスの形式が正しくなければ以降の処理を行わないように
- Fix: クリップの説明欄を更新する際に空にできない問題を修正
- Fix: フォロワーではないユーザーにリートもしくは返信された場合にートのDeleteアクティビティが送られていない問題を修正 - Fix: フォロワーではないユーザーにリートもしくは返信された場合にートのDeleteアクティビティが送られていない問題を修正
## 2025.2.0 ## 2025.2.0

View File

@ -39,7 +39,7 @@ export const paramDef = {
properties: { properties: {
name: { type: 'string', minLength: 1, maxLength: 100 }, name: { type: 'string', minLength: 1, maxLength: 100 },
isPublic: { type: 'boolean', default: false }, isPublic: { type: 'boolean', default: false },
description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 }, description: { type: 'string', nullable: true, maxLength: 2048 },
}, },
required: ['name'], required: ['name'],
} as const; } as const;
@ -53,7 +53,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
let clip: MiClip; let clip: MiClip;
try { try {
clip = await this.clipService.create(me, ps.name, ps.isPublic, ps.description ?? null); // 空文字列をnullにしたいので??は使わない
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
clip = await this.clipService.create(me, ps.name, ps.isPublic, ps.description || null);
} catch (e) { } catch (e) {
if (e instanceof ClipService.TooManyClipsError) { if (e instanceof ClipService.TooManyClipsError) {
throw new ApiError(meta.errors.tooManyClips); throw new ApiError(meta.errors.tooManyClips);

View File

@ -39,7 +39,7 @@ export const paramDef = {
clipId: { type: 'string', format: 'misskey:id' }, clipId: { type: 'string', format: 'misskey:id' },
name: { type: 'string', minLength: 1, maxLength: 100 }, name: { type: 'string', minLength: 1, maxLength: 100 },
isPublic: { type: 'boolean' }, isPublic: { type: 'boolean' },
description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 }, description: { type: 'string', nullable: true, maxLength: 2048 },
}, },
required: ['clipId'], required: ['clipId'],
} as const; } as const;
@ -53,7 +53,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
try { try {
await this.clipService.update(me, ps.clipId, ps.name, ps.isPublic, ps.description); // 空文字列をnullにしたいので??は使わない
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
await this.clipService.update(me, ps.clipId, ps.name, ps.isPublic, ps.description || null);
} catch (e) { } catch (e) {
if (e instanceof ClipService.NoSuchClipError) { if (e instanceof ClipService.NoSuchClipError) {
throw new ApiError(meta.errors.noSuchClip); throw new ApiError(meta.errors.noSuchClip);

View File

@ -182,7 +182,6 @@ describe('クリップ', () => {
{ label: 'nameがnull', parameters: { name: null } }, { label: 'nameがnull', parameters: { name: null } },
{ label: 'nameが最大長+1', parameters: { name: 'x'.repeat(101) } }, { label: 'nameが最大長+1', parameters: { name: 'x'.repeat(101) } },
{ label: 'isPublicがboolじゃない', parameters: { isPublic: 'true' } }, { label: 'isPublicがboolじゃない', parameters: { isPublic: 'true' } },
{ label: 'descriptionがゼロ長', parameters: { description: '' } },
{ label: 'descriptionが最大長+1', parameters: { description: 'a'.repeat(2049) } }, { label: 'descriptionが最大長+1', parameters: { description: 'a'.repeat(2049) } },
]; ];
test.each(createClipDenyPattern)('の作成は$labelならできない', async ({ parameters }) => failedApiCall({ test.each(createClipDenyPattern)('の作成は$labelならできない', async ({ parameters }) => failedApiCall({
@ -199,6 +198,23 @@ describe('クリップ', () => {
id: '3d81ceae-475f-4600-b2a8-2bc116157532', id: '3d81ceae-475f-4600-b2a8-2bc116157532',
})); }));
test('の作成はdescriptionが空文字ならnullになる', async () => {
const clip = await successfulApiCall({
endpoint: 'clips/create',
parameters: {
...defaultCreate(),
description: '',
},
user: alice,
});
assert.deepStrictEqual(clip, {
...clip,
...defaultCreate(),
description: null,
});
});
test('の更新ができる', async () => { test('の更新ができる', async () => {
const res = await update({ const res = await update({
clipId: (await create()).id, clipId: (await create()).id,
@ -249,6 +265,24 @@ describe('クリップ', () => {
...assertion, ...assertion,
})); }));
test('の更新はdescriptionが空文字ならnullになる', async () => {
const clip = await successfulApiCall({
endpoint: 'clips/update',
parameters: {
clipId: (await create()).id,
name: 'updated',
description: '',
},
user: alice,
});
assert.deepStrictEqual(clip, {
...clip,
name: 'updated',
description: null,
});
});
test('の削除ができる', async () => { test('の削除ができる', async () => {
await deleteClip({ await deleteClip({
clipId: (await create()).id, clipId: (await create()).id,