diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index a7e6aa5b3f..f62baf79e1 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -66,7 +66,7 @@ export const meta = { somethingHappenedInFetchingUri: { message: 'Something happened while fetching the URI.', code: 'SOMETHING_HAPPENED_IN_FETCHING_URI', - id: '14d45054-9df7-4f85-9e60-343b22f16b05, + id: '14d45054-9df7-4f85-9e60-343b22f16b05', }, }, diff --git a/packages/backend/test-federation/test/api/ap/show.test.ts b/packages/backend/test-federation/test/api/ap/show.test.ts new file mode 100644 index 0000000000..7c35026269 --- /dev/null +++ b/packages/backend/test-federation/test/api/ap/show.test.ts @@ -0,0 +1,66 @@ +import { strictEqual, rejects } from 'node:assert'; +import * as Misskey from 'misskey-js'; +import { createAccount, resolveRemoteUser, sleep, type LoginUser } from '../../utils.js'; + +describe('API ap/show', () => { + let alice: LoginUser, bob: LoginUser; + let bobInA: Misskey.entities.UserDetailedNotMe; + + beforeAll(async () => { + [alice, bob] = await Promise.all([ + createAccount('a.test'), + createAccount('b.test'), + ]); + + // 事前に bob を a.test に解決してキャッシュ・同一性を取る + bobInA = await resolveRemoteUser('b.test', bob.id, alice); + }); + + describe('User resolution', () => { + test('resolve by acct (bob@b.test)', async () => { + const res = await alice.client.request('ap/show', { uri: `${bob.username}@b.test` }); + strictEqual(res.type, 'User'); + strictEqual(res.object.id, bobInA.id); + strictEqual(res.object.uri, `https://b.test/users/${bob.id}`); + }); + + test('resolve by canonical user URL (https://b.test/users/:id)', async () => { + const res = await alice.client.request('ap/show', { uri: `https://b.test/users/${bob.id}` }); + strictEqual(res.type, 'User'); + strictEqual(res.object.id, bobInA.id); + strictEqual(res.object.uri, `https://b.test/users/${bob.id}`); + }); + + test('resolve by cross-origin non-canonical URL (https://a.test/@bob@b.test)', async () => { + const res = await alice.client.request('ap/show', { uri: `https://a.test/@${bob.username}@b.test` }); + strictEqual(res.type, 'User'); + // 非正規URLから正規IDに追従して同一ユーザーになること + strictEqual(res.object.id, bobInA.id); + strictEqual(res.object.uri, `https://b.test/users/${bob.id}`); + }); + + test('onlyUriFetch=true with acct string should fail with URI_INVALID', async () => { + await rejects( + async () => await alice.client.request('ap/show', { uri: `${bob.username}@b.test`, onlyUriFetch: true }), + (err: any) => { + strictEqual(err.code, 'URI_INVALID'); + return true; + }, + ); + }); + }); + + describe('Note resolution', () => { + test('resolve by note URL (https://b.test/notes/:id)', async () => { + const note = (await bob.client.request('notes/create', { text: 'hello from Bob' })).createdNote; + // 伝搬待ち + await sleep(); + + const res = await alice.client.request('ap/show', { uri: `https://b.test/notes/${note.id}` }); + strictEqual(res.type, 'Note'); + strictEqual(res.object.uri, `https://b.test/notes/${note.id}`); + // 投稿者が a.test 側で解決済みの Bob になること + strictEqual(res.object.userId, bobInA.id); + }); + }); +});