fix: classify malformed thread metadata

This commit is contained in:
2026-07-14 02:04:11 +09:00
parent f82f6641b2
commit d8827b6513
4 changed files with 58 additions and 6 deletions
+43
View File
@@ -153,6 +153,49 @@ describe('loadThreadPage', () => {
expect(client.getThreadPaged).not.toHaveBeenCalled()
})
it('classifies malformed Bird conversation metadata as an upstream failure', async () => {
const client = reader()
const privateMetadata = 'FINAL_PRIVATE_CONVERSATION_MARKER_71c9'
vi.mocked(client.getTweet).mockResolvedValue({
success: true,
tweet: {
id: '123',
text: 'focal',
conversationId: privateMetadata,
author: { username: 'focus', name: 'Focus' },
},
})
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
try {
const result = await loadThreadPage(client, { tweetId: '123' })
expect(result).toEqual({
ok: false,
error: {
code: 'upstream',
message: 'X から投稿を取得できませんでした。',
retryable: true,
},
})
expect(client.getTweet).toHaveBeenCalledWith('123')
expect(client.getThreadPaged).not.toHaveBeenCalled()
expect(errorSpy).toHaveBeenCalledTimes(1)
expect(errorSpy).toHaveBeenCalledWith(
'Unexpected Bird failure:',
expect.objectContaining({
message: '投稿 ID を確認してください。',
}),
)
expect(JSON.stringify(result)).not.toContain(privateMetadata)
expect(errorSpy.mock.calls.flat().join(' ')).not.toContain(
privateMetadata,
)
} finally {
errorSpy.mockRestore()
}
})
it('rejects an invalid ID without a Bird call', async () => {
const client = reader()
+7 -3
View File
@@ -133,9 +133,13 @@ export async function loadThreadPage(
return upstreamFailure(focal.error ?? 'Tweet not found', 'post')
}
focalPost = publicPost(focal.tweet)
conversationId = normalizeTweetId(
focal.tweet.conversationId ?? focal.tweet.id,
)
try {
conversationId = normalizeTweetId(
focal.tweet.conversationId ?? focal.tweet.id,
)
} catch (error) {
return upstreamFailure(error, 'post')
}
}
const result = await reader.getThreadPaged(conversationId, {