Files
SnowLuma/packages/onebot/tests/group-info-actions.test.ts
T
TanTan fa9c73363d
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
SnowLuma v2.4.2 source snapshot
2026-11-13 09:02:00 +08:00

167 lines
6.7 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest';
import type { GroupMemberInfo } from '@snowluma/protocol/qq-info';
import { ACTION_REGISTRY } from '../src/actions';
import {
getGroupMemberInfo as readGroupMemberInfo,
getGroupMemberList as readGroupMemberList,
} from '../src/modules/contact-actions';
const getGroupInfo = ACTION_REGISTRY.actions.find(
(action) => action.canonical === 'get_group_info',
)?.spec;
const getGroupList = ACTION_REGISTRY.actions.find(
(action) => action.canonical === 'get_group_list',
)?.spec;
const getGroupInfoEx = ACTION_REGISTRY.actions.find(
(action) => action.canonical === 'get_group_info_ex',
)?.spec;
const getGroupDetailInfo = ACTION_REGISTRY.actions.find(
(action) => action.canonical === 'get_group_detail_info',
)?.spec;
const getGroupMemberList = ACTION_REGISTRY.actions.find(
(action) => action.canonical === 'get_group_member_list',
)?.spec;
const getGroupMemberInfo = ACTION_REGISTRY.actions.find(
(action) => action.canonical === 'get_group_member_info',
)?.spec;
if (
!getGroupInfo ||
!getGroupList ||
!getGroupInfoEx ||
!getGroupDetailInfo ||
!getGroupMemberList ||
!getGroupMemberInfo
) {
throw new Error('group info actions missing');
}
function makeMember(overrides: Partial<GroupMemberInfo>): GroupMemberInfo {
return {
uin: 654321,
uid: 'u_member',
nickname: 'Member',
card: '',
isRobot: false,
role: 'member',
level: 1,
title: '',
joinTime: 0,
lastSentTime: 0,
shutUpTime: 0,
...overrides,
};
}
describe('group information actions', () => {
it('returns and documents the group-wide mute state', async () => {
const response = await getGroupInfo.toHandler({} as any)({ group_id: 123456 });
const infoSchema = getGroupInfo.describe().returnsSchema;
const listSchema = getGroupList.describe().returnsSchema;
expect(response).toMatchObject({
status: 'ok',
data: { group_id: 123456, group_all_shut: 0 },
});
expect(infoSchema?.properties).toHaveProperty('group_all_shut');
expect(infoSchema?.required).toContain('group_all_shut');
expect(listSchema?.items?.properties).toHaveProperty('group_all_shut');
expect(listSchema?.items?.required).toContain('group_all_shut');
});
it.each([
['get_group_info_ex', getGroupInfoEx],
['get_group_detail_info', getGroupDetailInfo],
])('%s exposes the shared group information result', async (_name, action) => {
const getGroupInfoResult = {
group_id: 123456,
group_name: 'Muted Group',
group_all_shut: -1,
};
const getGroupInfoProvider = vi.fn(async () => getGroupInfoResult);
const response = await action.toHandler({
getGroupInfo: getGroupInfoProvider,
} as any)({ group_id: 123456, no_cache: true });
expect(response).toMatchObject({ status: 'ok', data: getGroupInfoResult });
expect(getGroupInfoProvider).toHaveBeenCalledWith(123456, true);
expect(action.describe().returnsSchema?.properties).toHaveProperty('group_all_shut');
});
it('returns and documents member mute expiry across member actions', async () => {
const mutedUntil = 2_000_000_000;
const member = makeMember({ nickname: 'Muted Member', shutUpTime: mutedUntil });
const bridge = {
identity: {
uin: '123456',
findGroupMember: (groupId: number, userId: number) =>
groupId === 123456 && userId === member.uin ? member : null,
},
apis: { contacts: { fetchGroupMemberList: vi.fn(async () => [member]) } },
} as any;
const listResponse = await getGroupMemberList.toHandler({
getGroupMemberList: (groupId: number, noCache?: boolean) =>
readGroupMemberList(bridge, groupId, noCache),
} as any)({ group_id: 123456 });
const infoResponse = await getGroupMemberInfo.toHandler({
getGroupMemberInfo: (groupId: number, userId: number, noCache?: boolean) =>
readGroupMemberInfo(bridge, groupId, userId, noCache),
} as any)({ group_id: 123456, user_id: member.uin });
expect(listResponse).toMatchObject({
status: 'ok',
data: [{ user_id: member.uin, shut_up_timestamp: mutedUntil }],
});
expect(infoResponse).toMatchObject({
status: 'ok',
data: { user_id: member.uin, shut_up_timestamp: mutedUntil },
});
expect(getGroupMemberList.describe().returnsSchema?.items?.properties).toHaveProperty(
'shut_up_timestamp',
);
expect(getGroupMemberInfo.describe().returnsSchema?.properties).toHaveProperty(
'shut_up_timestamp',
);
});
it('returns a zero mute expiry when the member is unavailable', async () => {
const response = await getGroupMemberInfo.toHandler({
getGroupMemberInfo: vi.fn(async () => null),
} as any)({ group_id: 123456, user_id: 654323 });
expect(response).toMatchObject({
status: 'ok',
data: { user_id: 654323, shut_up_timestamp: 0 },
});
});
// #490: group_memo prefers the announcement, so the group description needs
// its own documented field.
it('documents the group description on both group-info results', async () => {
const infoSchema = getGroupInfo.describe().returnsSchema;
const listSchema = getGroupList.describe().returnsSchema;
expect(infoSchema?.properties).toHaveProperty('group_description');
expect(listSchema?.items?.properties).toHaveProperty('group_description');
// Never required: only the group list carries it, so a non-member lookup
// legitimately returns ''.
expect(infoSchema?.required ?? []).not.toContain('group_description');
// The no-provider fallback must still carry the key.
const response = await getGroupInfo.toHandler({} as any)({ group_id: 123456 });
expect(response).toMatchObject({ status: 'ok', data: { group_description: '' } });
});
it('documents qidian flags on both member-list and single-member results', () => {
const listSchema = getGroupMemberList.describe().returnsSchema;
const infoSchema = getGroupMemberInfo.describe().returnsSchema;
for (const schema of [listSchema?.items?.properties, infoSchema?.properties]) {
expect(schema).toHaveProperty('qidian_master_flag');
expect(schema).toHaveProperty('qidian_crew_flag');
expect(schema).toHaveProperty('qidian_crew_flag_2');
}
});
});