Files
SnowLuma/packages/core/tests/hex.test.ts
DrSmoothl 46b302f738 refactor(common): extract leaf utilities into @snowluma/common package
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.
2026-05-21 08:27:05 +08:00

66 lines
1.8 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { toHex, fromHex, hexPreview } from '@snowluma/common/hex';
describe('toHex', () => {
it('converts empty buffer', () => {
expect(toHex(new Uint8Array(0))).toBe('');
});
it('converts single byte', () => {
expect(toHex(new Uint8Array([0xff]))).toBe('ff');
expect(toHex(new Uint8Array([0x00]))).toBe('00');
expect(toHex(new Uint8Array([0x0a]))).toBe('0a');
});
it('converts multiple bytes', () => {
expect(toHex(new Uint8Array([0xde, 0xad, 0xbe, 0xef]))).toBe('deadbeef');
});
it('works with Buffer', () => {
expect(toHex(Buffer.from([0x01, 0x23, 0x45]))).toBe('012345');
});
});
describe('fromHex', () => {
it('converts empty string', () => {
expect(fromHex('')).toEqual(Buffer.alloc(0));
});
it('converts hex string', () => {
const buf = fromHex('deadbeef');
expect(buf).toEqual(Buffer.from([0xde, 0xad, 0xbe, 0xef]));
});
it('handles uppercase', () => {
const buf = fromHex('DEADBEEF');
expect(buf).toEqual(Buffer.from([0xde, 0xad, 0xbe, 0xef]));
});
it('throws on odd-length string', () => {
expect(() => fromHex('abc')).toThrow('Invalid hex string length');
});
it('roundtrips with toHex', () => {
const original = Buffer.from([0x00, 0x7f, 0x80, 0xff]);
expect(fromHex(toHex(original))).toEqual(original);
});
});
describe('hexPreview', () => {
it('shows full hex for small data', () => {
const data = new Uint8Array([0xab, 0xcd]);
expect(hexPreview(data)).toBe('abcd');
});
it('truncates with ellipsis', () => {
const data = new Uint8Array(128).fill(0xaa);
const preview = hexPreview(data, 4);
expect(preview).toBe('aaaaaaaa...');
});
it('no ellipsis when exactly at limit', () => {
const data = new Uint8Array(4).fill(0xbb);
expect(hexPreview(data, 4)).toBe('bbbbbbbb');
});
});