96 lines
4.6 KiB
TypeScript
96 lines
4.6 KiB
TypeScript
// StreamBasic must be evaluated before OneBotAction because of their circular import.
|
|
import '../napcat-onebot/action/stream/StreamBasic';
|
|
import { describe, expect, it, vi } from 'vitest';
|
|
import { TypeCompiler } from '@sinclair/typebox/compiler';
|
|
import { CommentQzone } from '@/napcat-onebot/action/extends/qzone/CommentQzone';
|
|
import { QzoneFeedReferenceExample, QzoneParentCommentSchema, QzoneUserSchema } from '@/napcat-onebot/action/extends/qzone/common';
|
|
import { normalizeQzoneCommentPayload } from '@/napcat-onebot/action/extends/qzone/common-snowluma';
|
|
import type { QzoneComment } from '@/napcat-core/data/qzone-rpc';
|
|
|
|
function createCore () {
|
|
return {
|
|
apis: {
|
|
QzoneApi: {
|
|
comment: vi.fn().mockResolvedValue({ comment_id: 'new-reply', comment: { id: 'new-reply' } }),
|
|
},
|
|
},
|
|
context: { logger: { logError: vi.fn() } },
|
|
};
|
|
}
|
|
|
|
function parentComment () {
|
|
return {
|
|
id: '0',
|
|
user: { uin: '10002', uid: null, nick: '作者', portrait: { type: 1, opaque: 'keep' } },
|
|
content: [{ type: 0, content: '原评论', extra: { keep: true } }],
|
|
replys: [{ id: 'inline-reply', user: { uid: null }, opaque_reply: [false, 0] }],
|
|
like: { id: 'raw-like-key', status: 0, count: 1 },
|
|
opaque_parent: { keep: ['9007199254740993', null] },
|
|
} satisfies QzoneComment;
|
|
}
|
|
|
|
describe('Qzone native parent with nullable raw UID', () => {
|
|
it('retains null UID and complete raw parent fields through the real handle parser', async () => {
|
|
const core = createCore();
|
|
const parent = parentComment();
|
|
const snapshot = structuredClone(parent);
|
|
const payload = { feed_ref: QzoneFeedReferenceExample, content: '回复', comment: parent };
|
|
const expectedPayload = structuredClone(payload);
|
|
|
|
const normalized = normalizeQzoneCommentPayload(payload);
|
|
expect(normalized.format).toBe('native');
|
|
if (normalized.format === 'native') expect(normalized.comment).toBe(parent);
|
|
const result = await new CommentQzone(undefined as never, core as never).handle(payload, 'test', {} as never);
|
|
|
|
expect(result).toMatchObject({ status: 'ok', data: { comment_id: 'new-reply' } });
|
|
expect(core.apis.QzoneApi.comment).toHaveBeenCalledExactlyOnceWith(QzoneFeedReferenceExample, '回复', snapshot, undefined);
|
|
expect(core.apis.QzoneApi.comment.mock.calls[0]?.[2]).toHaveProperty('user.uid', null);
|
|
expect(parent).toEqual(snapshot);
|
|
expect(parent.user.uid).toBeNull();
|
|
expect(payload).toEqual(expectedPayload);
|
|
});
|
|
|
|
it('keeps a nullable raw parent separate from an explicit reply target', async () => {
|
|
const core = createCore();
|
|
const parent = parentComment();
|
|
const snapshot = structuredClone(parent);
|
|
const target = { uin: '10003', uid: 'u_target', nick: '接收者', extra: { keep: true } };
|
|
const expectedTarget = structuredClone(target);
|
|
|
|
const result = await new CommentQzone(undefined as never, core as never).handle({
|
|
feed_ref: QzoneFeedReferenceExample, content: '指定回复', comment: parent, reply_user: target,
|
|
}, 'test', {} as never);
|
|
|
|
expect(result.status).toBe('ok');
|
|
expect(core.apis.QzoneApi.comment).toHaveBeenCalledExactlyOnceWith(QzoneFeedReferenceExample, '指定回复', snapshot, expectedTarget);
|
|
expect(parent).toEqual(snapshot);
|
|
expect(target).toEqual(expectedTarget);
|
|
});
|
|
|
|
it('allows null only in raw parent UID without relaxing parent identity or explicit target validation', async () => {
|
|
const parentSchema = TypeCompiler.Compile(QzoneParentCommentSchema);
|
|
const userSchema = TypeCompiler.Compile(QzoneUserSchema);
|
|
expect(parentSchema.Check(parentComment())).toBe(true);
|
|
expect(userSchema.Check({ uin: '10002', uid: null })).toBe(false);
|
|
const invalidParents = [
|
|
{ id: '0', user: { uin: '10002', uid: false } },
|
|
{ id: '0', user: { uin: '10002', uid: 0 } },
|
|
{ id: '0', user: { uin: '10002', uid: {} } },
|
|
{ id: 0, user: { uin: '10002', uid: null } },
|
|
{ id: '0', user: { uid: null } },
|
|
{ id: '0', user: { uin: Number.MAX_SAFE_INTEGER + 1, uid: null } },
|
|
];
|
|
const core = createCore();
|
|
const action = new CommentQzone(undefined as never, core as never);
|
|
for (const comment of invalidParents) {
|
|
const result = await action.handle({ feed_ref: QzoneFeedReferenceExample, content: '回复', comment } as never, 'test', {} as never);
|
|
expect(result.status).toBe('failed');
|
|
}
|
|
const invalidTarget = await action.handle({
|
|
feed_ref: QzoneFeedReferenceExample, content: '回复', comment: parentComment(), reply_user: { uin: '10003', uid: null },
|
|
} as never, 'test', {} as never);
|
|
expect(invalidTarget.status).toBe('failed');
|
|
expect(core.apis.QzoneApi.comment).not.toHaveBeenCalled();
|
|
});
|
|
});
|