Be better about identity resolution for stats view

This commit is contained in:
Jack Kingsman
2026-03-19 21:42:39 -07:00
parent b021a4a8ac
commit 33c2b0c948
4 changed files with 170 additions and 7 deletions
@@ -297,6 +297,54 @@ describe('RawPacketFeedView', () => {
expect(screen.getAllByText('Identity not resolvable').length).toBeGreaterThan(0);
});
it('collapses uniquely resolved hash buckets into the same visible contact row', () => {
const alphaContact = createContact({
public_key: 'aa11bb22cc33' + '0'.repeat(52),
name: 'Alpha',
});
renderView({
rawPacketStatsSession: createSession({
totalObservedPackets: 2,
observations: [
{
observationKey: 'obs-1',
timestamp: 1_700_000_000,
payloadType: 'TextMessage',
routeType: 'Direct',
decrypted: true,
rssi: -70,
snr: 6,
sourceKey: 'hash1:AA',
sourceLabel: 'AA',
pathTokenCount: 0,
pathSignature: null,
},
{
observationKey: 'obs-2',
timestamp: 1_700_000_030,
payloadType: 'TextMessage',
routeType: 'Direct',
decrypted: true,
rssi: -67,
snr: 7,
sourceKey: alphaContact.public_key.toUpperCase(),
sourceLabel: alphaContact.public_key.slice(0, 12).toUpperCase(),
pathTokenCount: 0,
pathSignature: null,
},
],
}),
contacts: [alphaContact],
});
fireEvent.click(screen.getByRole('button', { name: /show stats/i }));
fireEvent.change(screen.getByLabelText('Stats window'), { target: { value: 'session' } });
expect(screen.getAllByText('Alpha').length).toBeGreaterThan(0);
expect(screen.queryByText('Identity not resolvable')).not.toBeInTheDocument();
});
it('opens a packet detail modal from the raw feed and decrypts room messages when a key is loaded', () => {
renderView({
packets: [
+47
View File
@@ -2,8 +2,12 @@ import { describe, expect, it } from 'vitest';
import {
buildRawPacketStatsSnapshot,
summarizeRawPacketForStats,
type RawPacketStatsSessionState,
} from '../utils/rawPacketStats';
import type { RawPacket } from '../types';
const TEXT_MESSAGE_PACKET = '09046F17C47ED00A13E16AB5B94B1CC2D1A5059C6E5A6253C60D';
function createSession(
overrides: Partial<RawPacketStatsSessionState> = {}
@@ -75,6 +79,49 @@ function createSession(
}
describe('buildRawPacketStatsSnapshot', () => {
it('prefers decrypted contact identity over one-byte sourceHash for stats bucketing', () => {
const packet: RawPacket = {
id: 1,
observation_id: 10,
timestamp: 1_700_000_000,
data: TEXT_MESSAGE_PACKET,
payload_type: 'TextMessage',
snr: 4,
rssi: -72,
decrypted: true,
decrypted_info: {
channel_name: null,
sender: 'Alpha',
channel_key: null,
contact_key: '0a'.repeat(32),
},
};
const summary = summarizeRawPacketForStats(packet);
expect(summary.sourceKey).toBe('0A'.repeat(32));
expect(summary.sourceLabel).toBe('Alpha');
});
it('tags unresolved one-byte source hashes so they do not collide with full contact keys', () => {
const packet: RawPacket = {
id: 2,
observation_id: 11,
timestamp: 1_700_000_000,
data: TEXT_MESSAGE_PACKET,
payload_type: 'TextMessage',
snr: 4,
rssi: -72,
decrypted: false,
decrypted_info: null,
};
const summary = summarizeRawPacketForStats(packet);
expect(summary.sourceKey).toBe('hash1:0A');
expect(summary.sourceLabel).toBe('0A');
});
it('computes counts, rankings, and rolling-window coverage from session observations', () => {
const stats = buildRawPacketStatsSnapshot(createSession(), '5m', 1_000);