Files
hapi/ios/Packages/HapiKit/Tests/HapiProtocolTests/SessionSortingTests.swift
T
weishu 01dabad204 fix(sessions): stabilize ordering by latest user input
Persist and backfill the last user-message timestamp, and use it for deterministic session ordering across web and native clients while preserving manual pins.

Remove the fixed-order setting and guard ordering against transcript echoes, replayed messages, and stale REST/SSE updates. Add regression coverage and regenerate the SSE fixture.
2026-10-29 12:47:52 +08:00

48 lines
2.2 KiB
Swift

import Foundation
import HapiProtocol
import Testing
@Suite("User-activity session sorting")
struct SessionSortingTests {
private func summary(_ id: String, createdAt: Int = 0, at: Int? = nil) -> SessionSummary {
SessionSummary(id: id, active: false, thinking: false, activeAt: 0, updatedAt: 0,
createdAt: createdAt, lastUserMessageAt: at, metadataVersion: 0,
agentStateVersion: 0, todosUpdatedAt: 0, pendingRequestsCount: 0)
}
@Test func outputAndStatusDoNotReorderButNewInputDoes() {
var old = summary("old", createdAt: 1, at: 100)
let new = summary("new", createdAt: 2, at: 200)
old.updatedAt = 90_000
old.active = true
old.thinking = true
old.pendingRequestsCount = 4
#expect(sortSessionSummaries([old, new]).map(\.id) == ["new", "old"])
old.lastUserMessageAt = 300
#expect(sortSessionSummaries([old, new]).map(\.id) == ["old", "new"])
}
@Test func manualPinsStillComeFirst() {
var global = summary("global", at: 1)
global.globalPinned = true
var project = summary("project", at: 2)
project.pinned = true
#expect(sortSessionSummaries([summary("new", at: 99), project, global]).map(\.id) == ["global", "project", "new"])
}
@Test func creationFallbackAndTimestampTiesAreDeterministic() {
let rows = [summary("b", createdAt: 2), summary("a", createdAt: 2), summary("old", createdAt: 1)]
#expect(sortSessionSummaries(rows).map(\.id) == ["a", "b", "old"])
#expect(sortSessionSummaries(rows.reversed()).map(\.id) == ["a", "b", "old"])
}
@Test func patchClockIsMonotonicAndRenderRelevant() throws {
let row = summary("a", at: 100)
let patch = try JSONDecoder().decode(SessionPatch.self, from: Data("{\"lastUserMessageAt\":200}".utf8))
let next = SummaryPatching.applySessionSummaryPatch(row, patch)
#expect(next.lastUserMessageAt == 200)
#expect(!SummaryPatching.isRenderIrrelevantPatch(current: row, next: next))
#expect(SummaryPatching.applySessionSummaryPatch(next, SessionPatch(lastUserMessageAt: 50)).lastUserMessageAt == 200)
}
}