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,180 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import type { PacketSender } from '@snowluma/common/packet-sender';
|
||||
import { IdentityService } from '@snowluma/protocol/identity-service';
|
||||
import type {
|
||||
FriendInfo,
|
||||
GroupMemberInfo,
|
||||
QQGroupInfo,
|
||||
UserProfileInfo,
|
||||
} from '@snowluma/protocol/qq-info';
|
||||
import { Bridge } from '../src/bridge/bridge';
|
||||
|
||||
const sender: PacketSender = {
|
||||
sendPacket: async () => ({
|
||||
success: true,
|
||||
gotResponse: true,
|
||||
errorCode: 0,
|
||||
errorMessage: '',
|
||||
responseData: Buffer.alloc(0),
|
||||
}),
|
||||
};
|
||||
|
||||
function makeFriend(uin: number, uid: string, nickname: string): FriendInfo {
|
||||
return { uin, uid, nickname, remark: '' };
|
||||
}
|
||||
|
||||
function makeGroup(groupId: number): QQGroupInfo {
|
||||
return {
|
||||
groupId,
|
||||
groupName: 'group',
|
||||
remark: '',
|
||||
memberCount: 1,
|
||||
memberMax: 200,
|
||||
members: new Map(),
|
||||
};
|
||||
}
|
||||
|
||||
function makeMember(uin: number, uid: string): GroupMemberInfo {
|
||||
return {
|
||||
uin,
|
||||
uid,
|
||||
nickname: `nick-${uin}`,
|
||||
card: '',
|
||||
role: 'member',
|
||||
level: 0,
|
||||
title: '',
|
||||
joinTime: 0,
|
||||
lastSentTime: 0,
|
||||
shutUpTime: 0,
|
||||
};
|
||||
}
|
||||
|
||||
function makeProfile(uin: number, uid: string, nickname: string): UserProfileInfo {
|
||||
return {
|
||||
uin,
|
||||
uid,
|
||||
nickname,
|
||||
remark: '',
|
||||
qid: '',
|
||||
sex: 'unknown',
|
||||
age: 0,
|
||||
sign: '',
|
||||
avatar: '',
|
||||
level: 0,
|
||||
qidianMasterFlag: 0,
|
||||
qidianCrewFlag: 0,
|
||||
qidianCrewFlag2: 0,
|
||||
};
|
||||
}
|
||||
|
||||
describe('Bridge roster warmup', () => {
|
||||
it('settles all callers, including callers waiting before the first connection', async () => {
|
||||
const bridge = new Bridge(IdentityService.memory('10001'));
|
||||
const fetchFriends = vi
|
||||
.spyOn(bridge.apis.contacts, 'fetchFriendList')
|
||||
.mockResolvedValue([makeFriend(10001, 'u_self', 'me')]);
|
||||
vi.spyOn(bridge.apis.contacts, 'fetchGroupList').mockResolvedValue([]);
|
||||
const beforeLogin = bridge.whenRosterWarmupSettled();
|
||||
|
||||
bridge.bindPid(7, sender);
|
||||
const afterLogin = bridge.whenRosterWarmupSettled();
|
||||
const expected = { friendsLoaded: true, groupsLoaded: true };
|
||||
await expect(Promise.all([beforeLogin, afterLogin])).resolves.toEqual([expected, expected]);
|
||||
await expect(bridge.whenRosterWarmupSettled()).resolves.toEqual(expected);
|
||||
expect(fetchFriends).toHaveBeenCalledOnce();
|
||||
await bridge.dispose();
|
||||
await expect(bridge.whenRosterWarmupSettled()).resolves.toEqual(expected);
|
||||
});
|
||||
|
||||
it('releases warmup waiters when disposed before the first connection', async () => {
|
||||
const bridge = new Bridge(IdentityService.memory('10001'));
|
||||
const waiting = bridge.whenRosterWarmupSettled();
|
||||
await bridge.dispose();
|
||||
await expect(waiting).resolves.toEqual({ friendsLoaded: false, groupsLoaded: false });
|
||||
await expect(bridge.whenRosterWarmupSettled()).resolves.toEqual({
|
||||
friendsLoaded: false,
|
||||
groupsLoaded: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('fetches friends and groups after the first sender and writes Identity', async () => {
|
||||
const identity = IdentityService.memory('10001');
|
||||
const bridge = new Bridge(identity);
|
||||
const friends = [
|
||||
makeFriend(10001, 'u_self', 'me'),
|
||||
makeFriend(22222, 'u_friend', 'friend'),
|
||||
];
|
||||
const groups = [makeGroup(123)];
|
||||
const members = [makeMember(33333, 'u_member')];
|
||||
const fetchFriendList = vi
|
||||
.spyOn(bridge.apis.contacts, 'fetchFriendList')
|
||||
.mockImplementation(async () => {
|
||||
identity.rememberFriends(friends);
|
||||
return friends;
|
||||
});
|
||||
vi.spyOn(bridge.apis.contacts, 'fetchGroupList').mockImplementation(async () => {
|
||||
identity.rememberGroups(groups);
|
||||
return groups;
|
||||
});
|
||||
vi.spyOn(bridge.apis.contacts, 'fetchGroupMemberList').mockImplementation(
|
||||
async (groupId) => {
|
||||
identity.rememberGroupMembers(groupId, members);
|
||||
return members;
|
||||
},
|
||||
);
|
||||
|
||||
bridge.bindPid(7, sender);
|
||||
const result = await bridge.whenRosterWarmupSettled();
|
||||
|
||||
expect(result).toEqual({ friendsLoaded: true, groupsLoaded: true });
|
||||
expect(fetchFriendList).toHaveBeenCalledOnce();
|
||||
expect(identity.nickname).toBe('me');
|
||||
expect(identity.findFriend(22222)?.uid).toBe('u_friend');
|
||||
expect(identity.findGroup(123)?.groupName).toBe('group');
|
||||
expect(identity.findGroupMember(123, 33333)?.uid).toBe('u_member');
|
||||
await bridge.dispose();
|
||||
});
|
||||
|
||||
it('does not retry a failed friend list fetch', async () => {
|
||||
const identity = IdentityService.memory('10001');
|
||||
const bridge = new Bridge(identity);
|
||||
const fetchFriendList = vi
|
||||
.spyOn(bridge.apis.contacts, 'fetchFriendList')
|
||||
.mockRejectedValue(new Error('net'));
|
||||
vi.spyOn(bridge.apis.contacts, 'fetchGroupList').mockResolvedValue([]);
|
||||
vi.spyOn(bridge.apis.contacts, 'fetchUserProfile').mockResolvedValue(
|
||||
makeProfile(10001, 'u_self', 'me'),
|
||||
);
|
||||
|
||||
bridge.bindPid(7, sender);
|
||||
const result = await bridge.whenRosterWarmupSettled();
|
||||
|
||||
expect(result).toEqual({ friendsLoaded: false, groupsLoaded: true });
|
||||
expect(fetchFriendList).toHaveBeenCalledOnce();
|
||||
bridge.bindPid(8, sender);
|
||||
await bridge.whenRosterWarmupSettled();
|
||||
expect(fetchFriendList).toHaveBeenCalledOnce();
|
||||
await bridge.dispose();
|
||||
});
|
||||
|
||||
it('discards in-flight warmup writes after dispose', async () => {
|
||||
const identity = IdentityService.memory('10001');
|
||||
const bridge = new Bridge(identity);
|
||||
let releaseFriends!: (friends: FriendInfo[]) => void;
|
||||
const friendsGate = new Promise<FriendInfo[]>((resolve) => {
|
||||
releaseFriends = resolve;
|
||||
});
|
||||
vi.spyOn(bridge.apis.contacts, 'fetchFriendList').mockReturnValue(friendsGate);
|
||||
vi.spyOn(bridge.apis.contacts, 'fetchGroupList').mockResolvedValue([]);
|
||||
|
||||
bridge.bindPid(7, sender);
|
||||
const settled = bridge.whenRosterWarmupSettled();
|
||||
await bridge.dispose();
|
||||
releaseFriends([makeFriend(22222, 'u_friend', 'friend')]);
|
||||
|
||||
await expect(settled).resolves.toEqual({ friendsLoaded: false, groupsLoaded: false });
|
||||
expect(() => {
|
||||
identity.nickname = 'x';
|
||||
}).toThrow(/closed/);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user