test(chapters): twelve proposals each keep their own summons

Phase 6 of docs/chapter-uploads.md, which asked for three things to be
checked rather than assumed. All three already hold, so this is their tests
and the record of it.

The proposal list already groups: a "Waiting for you" header over the
sessions isAwaitingApproval picks out, "Everything else" under them, over a
query already ordered newest first. The chapter summary is already
`name - N words - N chunks` rather than "Chapter", so twelve rows are twelve
distinguishable rows. And isAbout already compares frostSigningSessionId when
both rows carry one, falling back to the clock only when either does not.

The last of those is the one with a history -- two concurrent sessions broke
it once, which is why the column exists -- so it is now tested at twelve, with
the answers given in the opposite order to the asking, so that neither the
clock nor the list order could produce the right answer by accident. Plus one
proposal failing and settling only its own row, which is the transcript's
version of why an upload is N sessions and not one.

Noted for Phase 8 rather than fixed here: twenty open chapter proposals is
roughly 900 KB of unsignedEventJson parsed and summarised on every emission of
that query, and summarize then re-splits each chapter's text to count its
chunks. Phase 8 replaces that derived count with the session's own item count.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kgothatso Ngako
2026-10-02 19:36:14 +02:00
parent 428026cdfd
commit f8a0ee1cdb
2 changed files with 67 additions and 13 deletions

View File

@@ -182,4 +182,42 @@ class TranscriptRequestStateTest {
assertEquals(setOf(hostKey.id), ChatMessage.answeredRequests(transcript))
}
@Test
fun `twelve open proposals each keep their own summons`() {
// An upload of twelve files opens twelve sessions at once, interleaved in
// one stretch of transcript. The clock fallback would have the first
// answer drop every later request, which is the failure two concurrent
// sessions already had once -- and twelve is where a path that still
// falls back to the clock would show.
val requests = (1..12).map { n ->
line(ChatMessage.TYPE_FROST_APPROVAL_NEEDED, at = 100L + n, session = "session-" + n)
}
// Answered in the opposite order to the asking, so neither the clock nor
// the list order could produce the right answer by accident.
val answers = listOf(12, 7, 1).map { n ->
line(ChatMessage.TYPE_FROST_NONCE, at = 200L + n, session = "session-" + n)
}
val answered = ChatMessage.answeredRequests(requests + answers)
assertEquals(
setOf(requests[11].id, requests[6].id, requests[0].id),
answered,
)
}
@Test
fun `one proposal failing settles only itself`() {
val requests = (1..12).map { n ->
line(ChatMessage.TYPE_FROST_APPROVAL_NEEDED, at = 100L + n, session = "session-" + n)
}
val failure = line(ChatMessage.TYPE_FROST_FAILED, at = 300, session = "session-5")
// The other eleven chapters are still in front of the group. A batch is
// all-or-nothing within a session and nothing across them, which is the
// whole reason an upload is N sessions rather than one.
assertEquals(setOf(requests[4].id), ChatMessage.settledRequests(requests + listOf(failure)))
}
}