186 lines
5.5 KiB
TypeScript
186 lines
5.5 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import {
|
|
areExpectedMembersEqual,
|
|
areLaunchSummaryCountsEqual,
|
|
areMemberSpawnSnapshotsSemanticallyEqual,
|
|
areMemberSpawnStatusEntriesEqual,
|
|
areMemberSpawnStatusesEqual,
|
|
} from '../../../src/renderer/store/team/teamMemberSpawnSnapshotEquality';
|
|
|
|
import type {
|
|
MemberSpawnStatusEntry,
|
|
MemberSpawnStatusesSnapshot,
|
|
PersistedTeamLaunchSummary,
|
|
} from '../../../src/shared/types';
|
|
|
|
function createSummary(
|
|
overrides: Partial<PersistedTeamLaunchSummary> = {}
|
|
): PersistedTeamLaunchSummary {
|
|
return {
|
|
confirmedCount: 1,
|
|
pendingCount: 0,
|
|
failedCount: 0,
|
|
skippedCount: 0,
|
|
runtimeAlivePendingCount: 0,
|
|
shellOnlyPendingCount: 0,
|
|
runtimeProcessPendingCount: 0,
|
|
runtimeCandidatePendingCount: 0,
|
|
noRuntimePendingCount: 0,
|
|
permissionPendingCount: 0,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function createStatusEntry(
|
|
overrides: Partial<MemberSpawnStatusEntry> = {}
|
|
): MemberSpawnStatusEntry {
|
|
return {
|
|
status: 'online',
|
|
launchState: 'confirmed_alive',
|
|
updatedAt: '2026-05-22T10:00:00.000Z',
|
|
livenessSource: 'heartbeat',
|
|
runtimeAlive: true,
|
|
runtimeModel: 'gpt-5.3-codex',
|
|
livenessKind: 'confirmed_bootstrap',
|
|
runtimeDiagnostic: 'Ready',
|
|
runtimeDiagnosticSeverity: 'info',
|
|
bootstrapConfirmed: true,
|
|
hardFailure: false,
|
|
pendingPermissionRequestIds: ['perm-a', 'perm-b'],
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function createSnapshot(
|
|
overrides: Partial<MemberSpawnStatusesSnapshot> = {}
|
|
): MemberSpawnStatusesSnapshot {
|
|
return {
|
|
statuses: {
|
|
alice: createStatusEntry(),
|
|
},
|
|
runId: 'run-1',
|
|
teamLaunchState: 'clean_success',
|
|
launchPhase: 'active',
|
|
expectedMembers: ['alice'],
|
|
updatedAt: '2026-05-22T10:00:00.000Z',
|
|
summary: createSummary(),
|
|
source: 'live',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('teamMemberSpawnSnapshotEquality', () => {
|
|
it('compares launch summaries by visible counts', () => {
|
|
expect(areLaunchSummaryCountsEqual(createSummary(), createSummary())).toBe(true);
|
|
expect(
|
|
areLaunchSummaryCountsEqual(createSummary(), createSummary({ permissionPendingCount: 1 }))
|
|
).toBe(false);
|
|
expect(areLaunchSummaryCountsEqual(undefined, undefined)).toBe(true);
|
|
expect(areLaunchSummaryCountsEqual(undefined, createSummary())).toBe(false);
|
|
});
|
|
|
|
it('compares expected members in stable order', () => {
|
|
expect(areExpectedMembersEqual(['alice', 'bob'], ['alice', 'bob'])).toBe(true);
|
|
expect(areExpectedMembersEqual(['alice', 'bob'], ['bob', 'alice'])).toBe(false);
|
|
expect(areExpectedMembersEqual(undefined, undefined)).toBe(true);
|
|
expect(areExpectedMembersEqual(undefined, [])).toBe(false);
|
|
});
|
|
|
|
it('ignores non-visible status churn and unordered pending permission ids', () => {
|
|
const left = createStatusEntry({
|
|
pendingPermissionRequestIds: ['perm-b', 'perm-a'],
|
|
updatedAt: '2026-05-22T10:00:00.000Z',
|
|
agentToolAccepted: true,
|
|
firstSpawnAcceptedAt: '2026-05-22T10:00:01.000Z',
|
|
lastHeartbeatAt: '2026-05-22T10:00:02.000Z',
|
|
livenessLastCheckedAt: '2026-05-22T10:00:03.000Z',
|
|
bootstrapStalled: true,
|
|
});
|
|
const right = createStatusEntry({
|
|
pendingPermissionRequestIds: ['perm-a', 'perm-b'],
|
|
updatedAt: '2026-05-22T10:05:00.000Z',
|
|
agentToolAccepted: false,
|
|
firstSpawnAcceptedAt: '2026-05-22T10:05:01.000Z',
|
|
lastHeartbeatAt: '2026-05-22T10:05:02.000Z',
|
|
livenessLastCheckedAt: '2026-05-22T10:05:03.000Z',
|
|
bootstrapStalled: false,
|
|
});
|
|
|
|
expect(areMemberSpawnStatusEntriesEqual(left, right)).toBe(true);
|
|
});
|
|
|
|
it('detects visible status entry changes', () => {
|
|
expect(
|
|
areMemberSpawnStatusEntriesEqual(
|
|
createStatusEntry(),
|
|
createStatusEntry({ runtimeDiagnosticSeverity: 'warning' })
|
|
)
|
|
).toBe(false);
|
|
expect(
|
|
areMemberSpawnStatusEntriesEqual(
|
|
createStatusEntry(),
|
|
createStatusEntry({ pendingPermissionRequestIds: ['perm-a'] })
|
|
)
|
|
).toBe(false);
|
|
});
|
|
|
|
it('compares per-member status maps by keys and semantic entries', () => {
|
|
expect(
|
|
areMemberSpawnStatusesEqual(
|
|
{
|
|
alice: createStatusEntry(),
|
|
bob: createStatusEntry({ runtimeModel: 'gpt-5.4' }),
|
|
},
|
|
{
|
|
bob: createStatusEntry({ runtimeModel: 'gpt-5.4' }),
|
|
alice: createStatusEntry(),
|
|
}
|
|
)
|
|
).toBe(true);
|
|
expect(
|
|
areMemberSpawnStatusesEqual(
|
|
{
|
|
alice: createStatusEntry(),
|
|
},
|
|
{
|
|
alice: createStatusEntry(),
|
|
bob: createStatusEntry(),
|
|
}
|
|
)
|
|
).toBe(false);
|
|
});
|
|
|
|
it('compares snapshots by semantic launch fields and ignores snapshot updatedAt churn', () => {
|
|
const left = createSnapshot({
|
|
updatedAt: '2026-05-22T10:00:00.000Z',
|
|
});
|
|
const right = createSnapshot({
|
|
updatedAt: '2026-05-22T10:05:00.000Z',
|
|
statuses: {
|
|
alice: createStatusEntry({
|
|
pendingPermissionRequestIds: ['perm-b', 'perm-a'],
|
|
updatedAt: '2026-05-22T10:05:00.000Z',
|
|
}),
|
|
},
|
|
});
|
|
|
|
expect(areMemberSpawnSnapshotsSemanticallyEqual(left, right)).toBe(true);
|
|
});
|
|
|
|
it('detects semantic snapshot changes', () => {
|
|
expect(
|
|
areMemberSpawnSnapshotsSemanticallyEqual(
|
|
createSnapshot(),
|
|
createSnapshot({ runId: 'run-2' })
|
|
)
|
|
).toBe(false);
|
|
expect(
|
|
areMemberSpawnSnapshotsSemanticallyEqual(
|
|
createSnapshot(),
|
|
createSnapshot({ expectedMembers: ['alice', 'bob'] })
|
|
)
|
|
).toBe(false);
|
|
expect(areMemberSpawnSnapshotsSemanticallyEqual(undefined, createSnapshot())).toBe(false);
|
|
});
|
|
});
|