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
215 lines
8.9 KiB
TypeScript
215 lines
8.9 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
import * as qzonePublic from '@snowluma/protocol/web/qzone-public';
|
|
import { QzoneApi } from '../../src/bridge/apis/qzone';
|
|
import { mockApiHub, mockBridge } from './_helpers';
|
|
|
|
const cookies = { p_skey: 'PSK' };
|
|
const feed = {
|
|
cell_common: { union_id: { feed_id: 'opaque-id' } },
|
|
cell_feed_transfer: { future: true },
|
|
};
|
|
const comment = { id: 'comment-id', user: { uid: 'commenter' }, unknown: [1, 2] };
|
|
const reply = { id: 'reply-id', user: { uid: 'recipient' } };
|
|
const content = [{ type: 0, content: 'text', future: { enabled: true } }];
|
|
const user = { uin: '10001', uid: 'self-uid', nick: 'self-nick' };
|
|
const result = { feeds: [], has_more: false, next_cursor: null, unknown: { preserved: true } };
|
|
|
|
function makeApi() {
|
|
const getCookies = vi.fn().mockResolvedValue(cookies);
|
|
const bridge = mockBridge({ apis: { ...mockApiHub(), web: { getCookies } } as never });
|
|
return { api: new QzoneApi(bridge as never), bridge, getCookies };
|
|
}
|
|
|
|
const cases = [
|
|
{
|
|
method: 'getQzoneHomeFeeds',
|
|
invoke: (api: QzoneApi) => api.getHomeFeeds(20002, 'cursor'),
|
|
args: [cookies, '10001', '20002', 'cursor'],
|
|
},
|
|
{
|
|
method: 'getQzonePublicCapabilities',
|
|
invoke: (api: QzoneApi) => api.getPublicCapabilities(),
|
|
args: [cookies, '10001'],
|
|
},
|
|
{
|
|
method: 'getQzonePublicFeeds',
|
|
invoke: (api: QzoneApi) => api.getPublicFeeds('cursor'),
|
|
args: [cookies, '10001', 'cursor'],
|
|
},
|
|
{
|
|
method: 'getQzonePublicFeedDetail',
|
|
invoke: (api: QzoneApi) => api.getPublicFeedDetail(feed),
|
|
args: [cookies, feed],
|
|
},
|
|
{
|
|
method: 'getQzonePublicComments',
|
|
invoke: (api: QzoneApi) => api.getPublicComments(feed, 'cursor'),
|
|
args: [cookies, feed, 'cursor'],
|
|
},
|
|
{
|
|
method: 'getQzonePublicReplies',
|
|
invoke: (api: QzoneApi) => api.getPublicReplies(feed, comment, 'cursor', 5),
|
|
args: [cookies, feed, comment, 'cursor', 5],
|
|
},
|
|
{
|
|
method: 'setQzonePublicUpvote',
|
|
invoke: (api: QzoneApi) => api.setPublicUpvote(feed, false),
|
|
args: [cookies, feed, false],
|
|
},
|
|
{
|
|
method: 'setQzonePublicLike',
|
|
invoke: (api: QzoneApi) => api.setPublicLike(feed, false),
|
|
args: [cookies, feed, false],
|
|
},
|
|
{
|
|
method: 'setQzonePublicCommentLike',
|
|
invoke: (api: QzoneApi) => api.setPublicCommentLike(feed, comment, false, reply),
|
|
args: [cookies, feed, comment, false, reply],
|
|
},
|
|
{
|
|
method: 'commentQzonePublicFeed',
|
|
invoke: (api: QzoneApi) => api.commentPublicFeed(feed, content),
|
|
args: [cookies, feed, user, content],
|
|
},
|
|
{
|
|
method: 'replyQzonePublicFeed',
|
|
invoke: (api: QzoneApi) => api.replyPublicFeed(feed, comment, content, reply),
|
|
args: [cookies, feed, user, comment, content, reply],
|
|
},
|
|
{
|
|
method: 'deleteQzonePublicComment',
|
|
invoke: (api: QzoneApi) => api.deletePublicComment(feed, comment),
|
|
args: [cookies, feed, comment],
|
|
},
|
|
{
|
|
method: 'deleteQzonePublicReply',
|
|
invoke: (api: QzoneApi) => api.deletePublicReply(feed, comment, reply),
|
|
args: [cookies, feed, comment, reply],
|
|
},
|
|
{
|
|
method: 'setQzonePublicFollow',
|
|
invoke: (api: QzoneApi) => api.setPublicFollow(feed, false),
|
|
args: [cookies, feed, false],
|
|
},
|
|
{
|
|
method: 'forwardQzonePublicFeed',
|
|
invoke: (api: QzoneApi) => api.forwardPublicFeed(feed, 'reason'),
|
|
args: [cookies, feed, 'reason'],
|
|
},
|
|
] as const;
|
|
|
|
describe('apis/qzone public', () => {
|
|
afterEach(() => vi.restoreAllMocks());
|
|
|
|
it('reads a post by tid using current-account credentials', async () => {
|
|
const { api, getCookies } = makeApi();
|
|
const tid = '1127000000f1536501000000';
|
|
const detail = { feed, attach_info: 'comments-next' };
|
|
const request = vi.spyOn(qzonePublic, 'getQzoneFeedDetail').mockResolvedValue(detail);
|
|
|
|
expect(await api.getFeedDetail(tid)).toBe(detail);
|
|
expect(getCookies).toHaveBeenCalledExactlyOnceWith('qzone.qq.com');
|
|
expect(request).toHaveBeenCalledExactlyOnceWith(cookies, tid);
|
|
});
|
|
|
|
it('propagates feed detail and cookie failures without replay', async () => {
|
|
const { api, getCookies } = makeApi();
|
|
const request = vi
|
|
.spyOn(qzonePublic, 'getQzoneFeedDetail')
|
|
.mockRejectedValue(new Error('denied'));
|
|
|
|
await expect(api.getFeedDetail('1127000000f1536501000000')).rejects.toThrow('denied');
|
|
getCookies.mockRejectedValueOnce(new Error('cookie failure'));
|
|
await expect(api.getFeedDetail('1127000000f1536501000000')).rejects.toThrow(
|
|
'cookie failure',
|
|
);
|
|
expect(request).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it('generates a share card with the account cookies and preserves its notice', async () => {
|
|
const { api, getCookies } = makeApi();
|
|
const card = { ark: '{}', requires_confirmation: true, notice: '所有人可见' };
|
|
const request = vi.spyOn(qzonePublic, 'getQzoneShareArk').mockResolvedValue(card);
|
|
expect(await api.getShareArk(feed)).toBe(card);
|
|
expect(getCookies).toHaveBeenCalledWith('qzone.qq.com');
|
|
expect(request).toHaveBeenCalledExactlyOnceWith(cookies, feed);
|
|
request.mockRejectedValueOnce(new Error('share denied'));
|
|
await expect(api.getShareArk(feed)).rejects.toThrow('share denied');
|
|
});
|
|
|
|
it.each(cases)(
|
|
'$method uses current-account cookies and preserves upstream objects',
|
|
async ({ method, invoke, args }) => {
|
|
const { api, getCookies } = makeApi();
|
|
const request = vi.spyOn(qzonePublic, method).mockResolvedValue(result);
|
|
|
|
expect(await invoke(api)).toBe(result);
|
|
expect(getCookies).toHaveBeenCalledWith('qzone.qq.com');
|
|
expect(request).toHaveBeenCalledExactlyOnceWith(...args);
|
|
},
|
|
);
|
|
|
|
it.each(cases)(
|
|
'$method propagates cookie and upstream failures without replay',
|
|
async ({ method, invoke }) => {
|
|
const { api, getCookies } = makeApi();
|
|
const request = vi
|
|
.spyOn(qzonePublic, method)
|
|
.mockRejectedValue(new Error('upstream denied'));
|
|
|
|
await expect(invoke(api)).rejects.toThrow('upstream denied');
|
|
getCookies.mockRejectedValueOnce(new Error('cookie failure'));
|
|
await expect(invoke(api)).rejects.toThrow('cookie failure');
|
|
expect(request).toHaveBeenCalledTimes(1);
|
|
},
|
|
);
|
|
|
|
it('keeps documented defaults and omits an optional reply target', async () => {
|
|
const { api } = makeApi();
|
|
const feeds = vi.spyOn(qzonePublic, 'getQzonePublicFeeds').mockResolvedValue(result);
|
|
const replies = vi.spyOn(qzonePublic, 'getQzonePublicReplies').mockResolvedValue(result);
|
|
const upvote = vi.spyOn(qzonePublic, 'setQzonePublicUpvote').mockResolvedValue(result);
|
|
const like = vi.spyOn(qzonePublic, 'setQzonePublicLike').mockResolvedValue(result);
|
|
const commentLike = vi
|
|
.spyOn(qzonePublic, 'setQzonePublicCommentLike')
|
|
.mockResolvedValue(result);
|
|
const follow = vi.spyOn(qzonePublic, 'setQzonePublicFollow').mockResolvedValue(result);
|
|
const forward = vi.spyOn(qzonePublic, 'forwardQzonePublicFeed').mockResolvedValue(result);
|
|
const replyRequest = vi
|
|
.spyOn(qzonePublic, 'replyQzonePublicFeed')
|
|
.mockResolvedValue(result);
|
|
|
|
await api.getPublicFeeds();
|
|
await api.getPublicReplies(feed, comment);
|
|
await api.setPublicUpvote(feed);
|
|
await api.setPublicLike(feed);
|
|
await api.setPublicCommentLike(feed, comment);
|
|
await api.setPublicFollow(feed);
|
|
await api.forwardPublicFeed(feed);
|
|
await api.replyPublicFeed(feed, comment, 'text');
|
|
|
|
expect(feeds).toHaveBeenCalledWith(cookies, '10001', undefined);
|
|
expect(replies).toHaveBeenCalledWith(cookies, feed, comment, '', 20);
|
|
expect(upvote).toHaveBeenCalledWith(cookies, feed, true);
|
|
expect(like).toHaveBeenCalledWith(cookies, feed, true);
|
|
expect(commentLike).toHaveBeenCalledWith(cookies, feed, comment, true, undefined);
|
|
expect(follow).toHaveBeenCalledWith(cookies, feed, true);
|
|
expect(forward).toHaveBeenCalledWith(cookies, feed, '');
|
|
expect(replyRequest).toHaveBeenCalledWith(cookies, feed, user, comment, 'text', undefined);
|
|
});
|
|
|
|
it('binds comment authors to the current account even when its uid is unavailable', async () => {
|
|
const { api, bridge } = makeApi();
|
|
Object.assign(bridge.identity, { selfUid: undefined });
|
|
const request = vi.spyOn(qzonePublic, 'commentQzonePublicFeed').mockResolvedValue(result);
|
|
|
|
await api.commentPublicFeed(feed, 'text');
|
|
expect(request).toHaveBeenCalledWith(
|
|
cookies,
|
|
feed,
|
|
{ uin: '10001', uid: '', nick: 'self-nick' },
|
|
'text',
|
|
);
|
|
});
|
|
});
|