Phase 1 of the 4-package split called out in RoadMap #3. Moves the "infrastructure" leaves — files with zero business dependencies that @snowluma/bridge, @snowluma/core, and @snowluma/onebot all consume — into a dedicated workspace package. Mirrors what #4 (proto-defs) did for wire-protobuf definitions. Why --- - Cleaner layering: these were never bridge- or onebot-specific. Logger / hex codec / runtime config sit at the bottom of every module's import graph; giving them their own package makes that graph readable and prevents future leaks the other direction. - Sets up #3's bridge + onebot extractions: those need a clean leaf to depend on. Without this, every cross-package import would have to re-export through core, which defeats the split. - Mirrors @snowluma/proto-defs layout — per-file subpath exports (`@snowluma/common/logger`, `@snowluma/common/hex`, …) keep import sites unambiguous and let bundlers tree-shake aggressively. Layout ------ Moved (via `git mv` — history preserved): packages/core/src/utils/logger.ts → @snowluma/common/logger packages/core/src/utils/hex.ts → @snowluma/common/hex packages/core/src/utils/log-file-transport.ts → @snowluma/common/log-file-transport packages/core/src/utils/log-summary.ts → @snowluma/common/log-summary packages/core/src/protocol/packet-sender.ts → @snowluma/common/packet-sender packages/core/src/protocol/types.ts → @snowluma/common/protocol-types packages/core/src/common/runtime.ts → @snowluma/common/runtime Not moved (deferred to Phase 2): `packages/core/src/utils/event-format.ts` — it has reverse imports on `bridge/events`, `bridge/identity-service`, `onebot/message-store`, `onebot/types`. It moves into @snowluma/bridge in Phase 2, with the MessageStore parameter decoupled via a structural type so bridge doesn't end up depending on onebot. Changes ------- - New package `packages/common/` (package.json, tsconfig.json — no src/index.ts barrel, callers use the subpath exports directly to avoid pulling in `node:fs` when only the hex codec is needed). - 7 `.ts` files moved via `git mv`. - 50+ import sites rewritten via sed across `packages/core/src` and `packages/core/tests`. Old shapes (`../utils/logger`, `../../protocol/types`, `./common/runtime`, `../src/utils/hex`) all collapse to `@snowluma/common/<sub>`. - `@snowluma/core` depends on `@snowluma/common` (workspace:*). - `packages/core/src/{protocol,common}/` directories removed (empty). Verification ------------ - core typecheck: clean - common typecheck: clean - Full workspace `pnpm -r typecheck`: all 9 projects clean - core test suite: 552 passed / 552 total (pre-existing websocket-darwin-arm64.node addon failure unchanged — same baseline as the previous refactor commits) - Vite build with stubbed native binary: clean (143 modules, 2.84 MB bundle, 224 KB gzip — same size envelope as before) Note: `utils/event-format.ts` is the only file remaining under the old `utils/` namespace. It moves into @snowluma/bridge alongside the events / identity-service / packet-pipeline trio in Phase 2.
170 lines
6.1 KiB
TypeScript
170 lines
6.1 KiB
TypeScript
// Shared fixtures for the per-theme bridge-action tests.
|
|
//
|
|
// Each action test file mocks `../src/bridge/bridge-oidb` and (where
|
|
// needed) `../src/bridge/highway/*` so we can assert what the action
|
|
// asked the OIDB / Highway layer to do without booting a real Bridge.
|
|
//
|
|
// `mockBridge()` returns a minimal stand-in: enough state for the
|
|
// actions to thread but no real packet/event machinery.
|
|
//
|
|
// As the #6 Api-on-ctx refactor moves business methods OFF Bridge and
|
|
// onto `bridge.apis.<area>.method()`, the mock grows an `apis` block
|
|
// matching the ApiHub shape. Each Api gets its own stub helper
|
|
// (e.g. `mockMessageApi()`) and tests can override individual entries
|
|
// via `mockBridge({ apis: { message: ... } })`.
|
|
|
|
import { vi } from 'vitest';
|
|
import type { SendPacketResult } from '@snowluma/common/packet-sender';
|
|
|
|
/** Default receipt returned by sendGroup / sendPrivate / sendC2cFile mocks. */
|
|
const STUB_RECEIPT = { messageId: 1, sequence: 1, clientSequence: 0, random: 1, timestamp: 0 };
|
|
|
|
export interface MockMessageApi {
|
|
sendGroup: ReturnType<typeof vi.fn>;
|
|
sendPrivate: ReturnType<typeof vi.fn>;
|
|
sendC2cFile: ReturnType<typeof vi.fn>;
|
|
recallGroup: ReturnType<typeof vi.fn>;
|
|
recallPrivate: ReturnType<typeof vi.fn>;
|
|
markGroupRead: ReturnType<typeof vi.fn>;
|
|
markPrivateRead: ReturnType<typeof vi.fn>;
|
|
}
|
|
|
|
export function mockMessageApi(): MockMessageApi {
|
|
return {
|
|
sendGroup: vi.fn(async () => STUB_RECEIPT),
|
|
sendPrivate: vi.fn(async () => STUB_RECEIPT),
|
|
sendC2cFile: vi.fn(async () => STUB_RECEIPT),
|
|
recallGroup: vi.fn(async () => undefined),
|
|
recallPrivate: vi.fn(async () => undefined),
|
|
markGroupRead: vi.fn(async () => undefined),
|
|
markPrivateRead: vi.fn(async () => undefined),
|
|
};
|
|
}
|
|
|
|
export interface MockContactsApi {
|
|
fetchFriendList: ReturnType<typeof vi.fn>;
|
|
fetchGroupList: ReturnType<typeof vi.fn>;
|
|
fetchGroupMemberList: ReturnType<typeof vi.fn>;
|
|
fetchUserProfile: ReturnType<typeof vi.fn>;
|
|
fetchGroupRequests: ReturnType<typeof vi.fn>;
|
|
fetchDownloadRKeys: ReturnType<typeof vi.fn>;
|
|
}
|
|
|
|
export function mockContactsApi(): MockContactsApi {
|
|
return {
|
|
fetchFriendList: vi.fn(async () => []),
|
|
fetchGroupList: vi.fn(async () => []),
|
|
fetchGroupMemberList: vi.fn(async () => []),
|
|
fetchUserProfile: vi.fn(async () => ({ uid: 'profile-uid' })),
|
|
fetchGroupRequests: vi.fn(async () => []),
|
|
fetchDownloadRKeys: vi.fn(async () => []),
|
|
};
|
|
}
|
|
|
|
export interface MockGroupFileApi {
|
|
upload: ReturnType<typeof vi.fn>;
|
|
uploadPrivate: ReturnType<typeof vi.fn>;
|
|
publish: ReturnType<typeof vi.fn>;
|
|
getCount: ReturnType<typeof vi.fn>;
|
|
list: ReturnType<typeof vi.fn>;
|
|
getUrl: ReturnType<typeof vi.fn>;
|
|
getPrivateUrl: ReturnType<typeof vi.fn>;
|
|
getPttUrl: ReturnType<typeof vi.fn>;
|
|
getPrivatePttUrl: ReturnType<typeof vi.fn>;
|
|
getVideoUrl: ReturnType<typeof vi.fn>;
|
|
getPrivateVideoUrl: ReturnType<typeof vi.fn>;
|
|
delete: ReturnType<typeof vi.fn>;
|
|
move: ReturnType<typeof vi.fn>;
|
|
createFolder: ReturnType<typeof vi.fn>;
|
|
deleteFolder: ReturnType<typeof vi.fn>;
|
|
renameFolder: ReturnType<typeof vi.fn>;
|
|
}
|
|
|
|
export function mockGroupFileApi(): MockGroupFileApi {
|
|
return {
|
|
upload: vi.fn(async () => ({ fileId: 'stub-fid' })),
|
|
uploadPrivate: vi.fn(async () => ({ fileId: 'stub-pfid', fileHash: 'stub-hash' })),
|
|
publish: vi.fn(async () => undefined),
|
|
getCount: vi.fn(async () => ({ fileCount: 0, maxCount: 10000 })),
|
|
list: vi.fn(async () => ({ files: [], folders: [] })),
|
|
getUrl: vi.fn(async () => 'stub://url'),
|
|
getPrivateUrl: vi.fn(async () => 'stub://private-url'),
|
|
getPttUrl: vi.fn(async () => 'stub://ptt-url'),
|
|
getPrivatePttUrl: vi.fn(async () => 'stub://private-ptt-url'),
|
|
getVideoUrl: vi.fn(async () => 'stub://video-url'),
|
|
getPrivateVideoUrl: vi.fn(async () => 'stub://private-video-url'),
|
|
delete: vi.fn(async () => undefined),
|
|
move: vi.fn(async () => undefined),
|
|
createFolder: vi.fn(async () => undefined),
|
|
deleteFolder: vi.fn(async () => undefined),
|
|
renameFolder: vi.fn(async () => undefined),
|
|
};
|
|
}
|
|
|
|
export interface MockApiHub {
|
|
message: MockMessageApi;
|
|
contacts: MockContactsApi;
|
|
groupFile: MockGroupFileApi;
|
|
// additional Apis added commit-by-commit as #6 progresses
|
|
}
|
|
|
|
export function mockApiHub(overrides: Partial<MockApiHub> = {}): MockApiHub {
|
|
return {
|
|
message: overrides.message ?? mockMessageApi(),
|
|
contacts: overrides.contacts ?? mockContactsApi(),
|
|
groupFile: overrides.groupFile ?? mockGroupFileApi(),
|
|
};
|
|
}
|
|
|
|
export interface MockBridge {
|
|
identity: {
|
|
uin: string;
|
|
selfUid: string;
|
|
nickname: string;
|
|
findUidByUin: ReturnType<typeof vi.fn>;
|
|
findUinByUid: ReturnType<typeof vi.fn>;
|
|
findGroupMember: ReturnType<typeof vi.fn>;
|
|
};
|
|
apis: MockApiHub;
|
|
sendRawPacket: ReturnType<typeof vi.fn>;
|
|
fetchFriendList: ReturnType<typeof vi.fn>;
|
|
fetchGroupMemberList: ReturnType<typeof vi.fn>;
|
|
fetchUserProfile: ReturnType<typeof vi.fn>;
|
|
resolveUserUid: ReturnType<typeof vi.fn>;
|
|
// Uploaded-file metadata cache helpers — GroupFileApi.upload /
|
|
// uploadPrivate call these to remember the upload, so tests
|
|
// covering those code paths get a default-no-op shim.
|
|
rememberUploadedFile: ReturnType<typeof vi.fn>;
|
|
recallUploadedFile: ReturnType<typeof vi.fn>;
|
|
}
|
|
|
|
export function mockBridge(overrides: Partial<MockBridge> = {}): MockBridge {
|
|
const defaultResp: SendPacketResult = {
|
|
success: true,
|
|
gotResponse: true,
|
|
errorCode: 0,
|
|
errorMessage: '',
|
|
responseData: Buffer.alloc(0),
|
|
};
|
|
return {
|
|
identity: {
|
|
uin: '10001',
|
|
selfUid: 'self-uid',
|
|
nickname: 'self-nick',
|
|
findUidByUin: vi.fn(() => 'cached-uid'),
|
|
findUinByUid: vi.fn(() => 0),
|
|
findGroupMember: vi.fn(() => null),
|
|
...(overrides.identity ?? {}),
|
|
} as MockBridge['identity'],
|
|
apis: overrides.apis ?? mockApiHub(),
|
|
sendRawPacket: vi.fn(async () => defaultResp),
|
|
fetchFriendList: vi.fn(async () => []),
|
|
fetchGroupMemberList: vi.fn(async () => []),
|
|
fetchUserProfile: vi.fn(async () => ({ uid: 'profile-uid' })),
|
|
resolveUserUid: vi.fn(async () => 'resolved-uid'),
|
|
rememberUploadedFile: vi.fn(),
|
|
recallUploadedFile: vi.fn(() => undefined),
|
|
...overrides,
|
|
};
|
|
}
|