SnowLuma v2.4.2 source snapshot
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
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
This commit is contained in:
commit
fa9c73363d
1367 files changed
+355735
No files matched your search
@@ -0,0 +1,166 @@
|
||||
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');
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user