Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
Publish MCP to npm / Build, test, and publish MCP (push) Waiting to run
Publish SDK to npm / Build, test, and publish SDK (push) Waiting to run
Sync action catalog to docs / sync (push) Waiting to run
Publish UI to npm / Build and publish UI (push) Waiting to run
Build and Release / preflight (push) Waiting to run
180 lines
7.4 KiB
TypeScript
180 lines
7.4 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
import { getQzoneFeedDetail, getQzoneShareArk } from '../../src/web/qzone-public';
|
|
import { RequestUtil, getBknFromCookie } from '../../src/web/request-util';
|
|
|
|
const cookies = { p_skey: 'fixture', uin: 'o20002' };
|
|
// Synthetic post: author 10001, Unix time 1700000000, sequence word 1.
|
|
const tid = '1127000000f1536501000000';
|
|
const ugcId = 'CLcCEJFOGIDiz6oGIgAqADIBMEIYMTEyNzAwMDAwMGYxNTM2NTAxMDAwMDAw';
|
|
const feed = {
|
|
cell_common: { domain_type: 1, union_id: { feed_id: tid, ugc_id: ugcId }, operate_mask: 0 },
|
|
cell_summary: { summary: 'Fixture post' },
|
|
cell_pic: { pics: [{ url: 'https://example.com/one.png' }, { future_image: true }] },
|
|
cell_video: { opaque_video: 'preserved' },
|
|
cell_comment: { comments: [{ id: '9007199254740993', unknown: true }] },
|
|
cell_operation: {
|
|
share_outside: {
|
|
share_access_resource: { busi_id: 'qzone_feed', resource_id: 'resource' },
|
|
},
|
|
},
|
|
cell_feed_transfer: 'original-transfer',
|
|
cell_future: { nested: [null, '18446744073709551615', { preserved: true }] },
|
|
};
|
|
const detail = {
|
|
feed: { feed },
|
|
attach_info: 'opaque-comment-continuation',
|
|
contribute_bar: null,
|
|
future_detail_field: { preserved: true },
|
|
};
|
|
|
|
afterEach(() => vi.restoreAllMocks());
|
|
|
|
describe('Qzone feed detail by tid', () => {
|
|
it.each([tid, tid.toUpperCase()])(
|
|
'requests the personal-space detail with the encoded identity (%s)',
|
|
async (input) => {
|
|
const request = vi
|
|
.spyOn(RequestUtil, 'HttpGetJson')
|
|
.mockResolvedValue({ retcode: 0, data: detail });
|
|
const result = await getQzoneFeedDetail(cookies, input);
|
|
|
|
expect(result).toEqual({ ...detail, feed });
|
|
expect(result.feed).toBe(feed);
|
|
expect(request).toHaveBeenCalledExactlyOnceWith(
|
|
`https://h5.qzone.qq.com/http2rpc/gotrpc/v1/trpc.qzone.feeds_reader.FeedsReader/GetFeedDetail?g_tk=${getBknFromCookie(cookies)}`,
|
|
'POST',
|
|
{
|
|
union_id: { feed_id: tid, ugc_id: ugcId, adv_id: null },
|
|
feed_action_domain_type: 1,
|
|
from_page: 1,
|
|
},
|
|
expect.objectContaining({
|
|
Cookie: 'p_skey=fixture; uin=o20002',
|
|
'Content-Type': 'application/json',
|
|
'x-oidb': JSON.stringify({
|
|
uint32_command: '0x97b1',
|
|
uint32_service_type: '1',
|
|
}),
|
|
}),
|
|
);
|
|
},
|
|
);
|
|
|
|
it('keeps the author and time unsigned and emits the explicit empty identity fields', async () => {
|
|
const highTid = 'ffffffffffffffff01000000';
|
|
const request = vi.spyOn(RequestUtil, 'HttpGetJson').mockResolvedValue({
|
|
retcode: 0,
|
|
data: { feed: { feed: { cell_common: { union_id: { feed_id: highTid } } } } },
|
|
});
|
|
|
|
await getQzoneFeedDetail(cookies, highTid);
|
|
const body = request.mock.calls[0]![2] as { union_id: { ugc_id: string } };
|
|
// Independent wire vector: app 311; two uint32-max values encoded as
|
|
// positive int64; present empty strings in fields 4 and 5; field 8 tid.
|
|
expect(Buffer.from(body.union_id.ugc_id, 'base64').toString('hex')).toBe(
|
|
'08b70210ffffffff0f18ffffffff0f22002a003201304218' +
|
|
'666666666666666666666666666666663031303030303030',
|
|
);
|
|
});
|
|
|
|
it.each([undefined, null, ''])(
|
|
'preserves an empty or absent upstream comment cursor (%s)',
|
|
async (attach_info) => {
|
|
const request = vi.spyOn(RequestUtil, 'HttpGetJson').mockResolvedValue({
|
|
retcode: 0,
|
|
data: { ...detail, attach_info },
|
|
});
|
|
|
|
expect((await getQzoneFeedDetail(cookies, tid)).attach_info).toBe(attach_info ?? null);
|
|
expect(request).toHaveBeenCalledOnce();
|
|
},
|
|
);
|
|
|
|
it.each(['', 'T1', 'g'.repeat(24), 'a'.repeat(23), 'a'.repeat(25), `${tid}\n`, ` ${tid}`])(
|
|
'rejects unsupported or malformed post IDs before requesting (%j)',
|
|
async (input) => {
|
|
const request = vi.spyOn(RequestUtil, 'HttpGetJson');
|
|
|
|
await expect(getQzoneFeedDetail(cookies, input)).rejects.toThrow('24 位十六进制');
|
|
expect(request).not.toHaveBeenCalled();
|
|
},
|
|
);
|
|
|
|
it.each([
|
|
{},
|
|
{ feed: null },
|
|
{ feed: [] },
|
|
{ feed: { feed: [] } },
|
|
{ feed: { feed: {} } },
|
|
{ feed: { feed: { cell_common: { union_id: { feed_id: 'other' } } } } },
|
|
{ ...detail, attach_info: {} },
|
|
])('rejects incomplete, mismatched or malformed details (%#)', async (data) => {
|
|
const request = vi
|
|
.spyOn(RequestUtil, 'HttpGetJson')
|
|
.mockResolvedValue({ retcode: 0, data });
|
|
|
|
await expect(getQzoneFeedDetail(cookies, tid)).rejects.toThrow('GetFeedDetail');
|
|
expect(request).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it('propagates access and transport failures without a second request', async () => {
|
|
const request = vi
|
|
.spyOn(RequestUtil, 'HttpGetJson')
|
|
.mockResolvedValueOnce({ retcode: 10023, message: '无权限访问' })
|
|
.mockRejectedValueOnce(new Error('network down'));
|
|
|
|
await expect(getQzoneFeedDetail(cookies, tid)).rejects.toThrow('10023 无权限访问');
|
|
expect(request).toHaveBeenCalledTimes(1);
|
|
await expect(getQzoneFeedDetail(cookies, tid)).rejects.toThrow('network down');
|
|
expect(request).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it('passes the returned feed straight to card generation without rebuilding sharing data', async () => {
|
|
const ark = '{ "app": "com.tencent.tuwen.lua", "fixture": true }';
|
|
const request = vi
|
|
.spyOn(RequestUtil, 'HttpGetJson')
|
|
.mockResolvedValueOnce({ retcode: 0, data: detail })
|
|
.mockResolvedValueOnce({ retcode: 0, data: { template_data: ark } });
|
|
const result = await getQzoneFeedDetail(cookies, tid);
|
|
|
|
await expect(getQzoneShareArk(cookies, result.feed)).resolves.toEqual({
|
|
ark,
|
|
requires_confirmation: false,
|
|
notice: null,
|
|
});
|
|
expect(request).toHaveBeenCalledTimes(2);
|
|
expect(request).toHaveBeenNthCalledWith(
|
|
2,
|
|
expect.stringContaining('/trpc.qq.share.Service/CreateShare?g_tk='),
|
|
'POST',
|
|
{
|
|
info: {
|
|
busi_id: 'qzone_feed',
|
|
resource_id: 'resource',
|
|
template: 'ark_busi_select',
|
|
busi_data: 'original-transfer',
|
|
plat: 1,
|
|
target: 3,
|
|
},
|
|
},
|
|
expect.any(Object),
|
|
);
|
|
});
|
|
|
|
it('can read a share-restricted feed while preserving the share refusal', async () => {
|
|
const restrictedFeed = {
|
|
...feed,
|
|
cell_common: { ...feed.cell_common, operate_mask: 1 << 17 },
|
|
};
|
|
const request = vi.spyOn(RequestUtil, 'HttpGetJson').mockResolvedValue({
|
|
retcode: 0,
|
|
data: { feed: { feed: restrictedFeed } },
|
|
});
|
|
const result = await getQzoneFeedDetail(cookies, tid);
|
|
|
|
expect(result.feed).toBe(restrictedFeed);
|
|
await expect(getQzoneShareArk(cookies, result.feed)).rejects.toThrow('不支持分享');
|
|
expect(request).toHaveBeenCalledOnce();
|
|
});
|
|
});
|