nandi/frqpublic Fork 0
ec74784743600d5adda1b7d2ec1f9eb2a6bc23ca
Commits
Clone
git clone https://git.rickub.com/nandi/frq.git
git clone ssh://git@rickub.com/nandi/frq.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Ask for the backlog, and survive hanging up 6214c7e · on ec74784743600d5adda1b7d2ec1f9eb2a6bc23ca · nandi · 12h ago
tsession.nim · 106 lines · 4.0 KBNim Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
## What this client says back to a server, given what a server says to it.
##
## The reducer's answer to an incoming line was the largest untested thing in
## the program: reachable only through a real socket, so nothing checked it.
## That is where the missing CHATHISTORY request sat through the whole port —
## every room came back with no history and the only comment about it was in
## another module, describing behaviour that had never been ported.
##
## `conn.feed` puts a line in as though the server had sent it; `tryOutbound`
## reads what went out. No socket at either end.

import std/[sequtils, strutils, tables, unittest]
import frq/[cells, model, reducer, rooms]
import frq/conn as tr

proc sent(): seq[string] =
  ## Everything queued to go out, drained.
  while true:
    let (ok, line) = tr.tryOutbound()
    if not ok: break
    result.add line

proc say(lines: varargs[string]) =
  for l in lines: tr.feed(l)
  drain()

proc reset() =
  app = initState()
  app.formNick = "alice"
  discard sent()

proc joined(room: string) =
  ## The server putting us in a room, which is how every one of them starts —
  ## a JOIN we sent, or one freeq made for a signed-in account at
  ## registration. A 366 for a room this client has never heard of is not
  ## ours and is left alone.
  say(":alice!a@h JOIN " & room)
  discard sent()

suite "asking for the backlog":
  setup: reset()

  test "end of NAMES in an empty room asks for history":
    joined("#freeq")
    # freeq re-joins an authenticated user's channels at registration and
    # leaves the backlog for the client to ask for. A room that arrives this
    # way has no history coming unless we ask, which is why a signed-in
    # connection showed new lines and nothing else.
    say(":server 366 alice #freeq :End of /NAMES list")
    check app.rooms.hasKey("#freeq")
    check sent().anyIt(it.startsWith("CHATHISTORY LATEST #freeq * "))

  test "and asks for a hundred lines of it":
    joined("#freeq")
    say(":server 366 alice #freeq :End of /NAMES list")
    check "CHATHISTORY LATEST #freeq * 100" in sent()

  test "a room that already has lines is not asked twice":
    # The replay comes back as ordinary PRIVMSGs; asking again is a second
    # copy of the same history crossing the wire to be thrown away.
    joined("#freeq")
    say(":bob!b@h PRIVMSG #freeq :already here")
    discard sent()
    say(":server 366 alice #freeq :End of /NAMES list")
    check not sent().anyIt(it.startsWith("CHATHISTORY"))

  test "a room this client was never put in is not asked about":
    # Not a room of ours: 366 for it arrives before any JOIN, and answering
    # it would ask a server for the history of somewhere we are not.
    say(":server 366 alice #nowhere :End of /NAMES list")
    check not app.rooms.hasKey("#nowhere")
    check not sent().anyIt(it.startsWith("CHATHISTORY"))

suite "the rest of the conversation":
  setup: reset()

  test "a PING is answered with its own token":
    say("PING :abc123")
    check "PONG :abc123" in sent()

  test "our own JOIN marks the room joined; somebody else's adds a name":
    say(":alice!a@h JOIN #freeq")
    check app.rooms["#freeq"].joined
    say(":bob!b@h JOIN #freeq")
    check app.rooms["#freeq"].users.hasKey("bob")

  test "NAMES arrives over several lines and lands in one go":
    joined("#freeq")
    say(":server 353 alice = #freeq :alice @bob",
        ":server 353 alice = #freeq :carol")
    # Still pending: replacing the list per line empties the panel and
    # refills it a name at a time.
    check app.rooms["#freeq"].users.len == 0
    say(":server 366 alice #freeq :End of /NAMES list")
    check app.rooms["#freeq"].users.len == 3

  test "a replayed line lands in the room it names":
    joined("#freeq")
    say(":server 366 alice #freeq :End of /NAMES list",
        ":bob!b@h PRIVMSG #freeq :an old line")
    check app.rooms["#freeq"].messages.anyIt(it.text == "an old line")

  test "a message to us is filed under whoever sent it":
    say(":bob!b@h PRIVMSG alice :a direct word")
    check app.rooms.hasKey("bob")
    check app.rooms["bob"].messages[^1].text == "a direct word"