fix(toutiao): fail publish when body images fail to upload
Route content/cover images through the desktop media-image asset pipeline and abort before submitting when any body image was not replaced with a Toutiao-hosted URL, so we don't publish articles that reference our own private asset endpoints. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import {
|
||||
publishToutiaoArticle,
|
||||
type ToutiaoPublishTransport,
|
||||
} from '../../../../../packages/publisher-platforms/src/toutiao'
|
||||
|
||||
function createTransport(
|
||||
overrides: Partial<ToutiaoPublishTransport> = {},
|
||||
): ToutiaoPublishTransport & { submitCalls: URLSearchParams[] } {
|
||||
const submitCalls: URLSearchParams[] = []
|
||||
const transport: ToutiaoPublishTransport & { submitCalls: URLSearchParams[] } = {
|
||||
submitCalls,
|
||||
async fetchJson(input, init) {
|
||||
if (input.includes('/media/get_media_info')) {
|
||||
return {
|
||||
data: {
|
||||
user: {
|
||||
id_str: 'uid-1',
|
||||
screen_name: '头条号测试账号',
|
||||
},
|
||||
media: {},
|
||||
},
|
||||
} as never
|
||||
}
|
||||
|
||||
if (input.includes('/spice/image')) {
|
||||
return {
|
||||
data: {
|
||||
image_url: 'https://p3-sign.toutiaoimg.com/tos-cn-i-body/image.png',
|
||||
image_uri: 'tos-cn-i-body/image.png',
|
||||
image_width: 640,
|
||||
image_height: 360,
|
||||
},
|
||||
} as never
|
||||
}
|
||||
|
||||
if (input.includes('/article/publish')) {
|
||||
submitCalls.push(init?.body as URLSearchParams)
|
||||
return {
|
||||
code: 0,
|
||||
data: {
|
||||
pgc_id: 'pgc-1',
|
||||
},
|
||||
} as never
|
||||
}
|
||||
|
||||
return {} as never
|
||||
},
|
||||
async fetchImageBlob() {
|
||||
return new Blob(['image'], { type: 'image/png' })
|
||||
},
|
||||
async uploadHtmlImages(html, uploader) {
|
||||
const source = '/api/public/assets/body-token'
|
||||
const target = await uploader(source)
|
||||
return {
|
||||
html: target ? html.split(source).join(target) : html,
|
||||
uploaded: target ? new Map([[source, target]]) : new Map(),
|
||||
}
|
||||
},
|
||||
...overrides,
|
||||
}
|
||||
return transport
|
||||
}
|
||||
|
||||
describe('publishToutiaoArticle', () => {
|
||||
it('submits content with body images replaced by Toutiao image URLs', async () => {
|
||||
const transport = createTransport()
|
||||
|
||||
const result = await publishToutiaoArticle(
|
||||
{
|
||||
title: '标题',
|
||||
html: '<p><img src="/api/public/assets/body-token" alt="" /></p>',
|
||||
publishType: 'publish',
|
||||
},
|
||||
transport,
|
||||
)
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
expect(transport.submitCalls).toHaveLength(1)
|
||||
expect(transport.submitCalls[0]?.get('content')).toContain('https://p3-sign.toutiaoimg.com')
|
||||
expect(transport.submitCalls[0]?.get('content')).not.toContain('/api/public/assets/body-token')
|
||||
})
|
||||
|
||||
it('fails before submit when any body image cannot be uploaded to Toutiao', async () => {
|
||||
const transport = createTransport({
|
||||
async uploadHtmlImages(html) {
|
||||
return {
|
||||
html,
|
||||
uploaded: new Map(),
|
||||
}
|
||||
},
|
||||
})
|
||||
const fetchJson = vi.spyOn(transport, 'fetchJson')
|
||||
|
||||
const result = await publishToutiaoArticle(
|
||||
{
|
||||
title: '标题',
|
||||
html: '<p><img src="/api/public/assets/body-token" alt="" /></p>',
|
||||
publishType: 'publish',
|
||||
},
|
||||
transport,
|
||||
)
|
||||
|
||||
expect(result).toMatchObject({
|
||||
success: false,
|
||||
status: 'failed',
|
||||
code: 'toutiaohao_image_upload_failed',
|
||||
})
|
||||
expect(fetchJson.mock.calls.some(([input]) => input.includes('/article/publish'))).toBe(false)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user