| Ask for the backlog, and survive hanging up 6214c7e nandi 16h ago | 1 | ## What this client says back to a server, given what a server says to it. |
| 2 | ## |
| 3 | ## The reducer's answer to an incoming line was the largest untested thing in |
| 4 | ## the program: reachable only through a real socket, so nothing checked it. |
| 5 | ## That is where the missing CHATHISTORY request sat through the whole port — |
| 6 | ## every room came back with no history and the only comment about it was in |
| 7 | ## another module, describing behaviour that had never been ported. |
| 8 | ## |
| 9 | ## `conn.feed` puts a line in as though the server had sent it; `tryOutbound` |
| 10 | ## reads what went out. No socket at either end. |
| 11 | |
| 12 | import std/[sequtils, strutils, tables, unittest] |
| 13 | import frq/[cells, model, reducer, rooms] |
| 14 | import frq/conn as tr |
| 15 | |
| 16 | proc sent(): seq[string] = |
| 17 | ## Everything queued to go out, drained. |
| 18 | while true: |
| 19 | let (ok, line) = tr.tryOutbound() |
| 20 | if not ok: break |
| 21 | result.add line |
| 22 | |
| 23 | proc say(lines: varargs[string]) = |
| 24 | for l in lines: tr.feed(l) |
| 25 | drain() |
| 26 | |
| 27 | proc reset() = |
| 28 | app = initState() |
| 29 | app.formNick = "alice" |
| 30 | discard sent() |
| 31 | |
| 32 | proc joined(room: string) = |
| 33 | ## The server putting us in a room, which is how every one of them starts — |
| 34 | ## a JOIN we sent, or one freeq made for a signed-in account at |
| 35 | ## registration. A 366 for a room this client has never heard of is not |
| 36 | ## ours and is left alone. |
| 37 | say(":alice!a@h JOIN " & room) |
| 38 | discard sent() |
| 39 | |
| 40 | suite "asking for the backlog": |
| 41 | setup: reset() |
| 42 | |
| 43 | test "end of NAMES in an empty room asks for history": |
| 44 | joined("#freeq") |
| 45 | # freeq re-joins an authenticated user's channels at registration and |
| 46 | # leaves the backlog for the client to ask for. A room that arrives this |
| 47 | # way has no history coming unless we ask, which is why a signed-in |
| 48 | # connection showed new lines and nothing else. |
| 49 | say(":server 366 alice #freeq :End of /NAMES list") |
| 50 | check app.rooms.hasKey("#freeq") |
| 51 | check sent().anyIt(it.startsWith("CHATHISTORY LATEST #freeq * ")) |
| 52 | |
| 53 | test "and asks for a hundred lines of it": |
| 54 | joined("#freeq") |
| 55 | say(":server 366 alice #freeq :End of /NAMES list") |
| 56 | check "CHATHISTORY LATEST #freeq * 100" in sent() |
| 57 | |
| 58 | test "a room that already has lines is not asked twice": |
| 59 | # The replay comes back as ordinary PRIVMSGs; asking again is a second |
| 60 | # copy of the same history crossing the wire to be thrown away. |
| 61 | joined("#freeq") |
| 62 | say(":bob!b@h PRIVMSG #freeq :already here") |
| 63 | discard sent() |
| 64 | say(":server 366 alice #freeq :End of /NAMES list") |
| 65 | check not sent().anyIt(it.startsWith("CHATHISTORY")) |
| 66 | |
| 67 | test "a room this client was never put in is not asked about": |
| 68 | # Not a room of ours: 366 for it arrives before any JOIN, and answering |
| 69 | # it would ask a server for the history of somewhere we are not. |
| 70 | say(":server 366 alice #nowhere :End of /NAMES list") |
| 71 | check not app.rooms.hasKey("#nowhere") |
| 72 | check not sent().anyIt(it.startsWith("CHATHISTORY")) |
| 73 | |
| 74 | suite "the rest of the conversation": |
| 75 | setup: reset() |
| 76 | |
| 77 | test "a PING is answered with its own token": |
| 78 | say("PING :abc123") |
| 79 | check "PONG :abc123" in sent() |
| 80 | |
| 81 | test "our own JOIN marks the room joined; somebody else's adds a name": |
| 82 | say(":alice!a@h JOIN #freeq") |
| 83 | check app.rooms["#freeq"].joined |
| 84 | say(":bob!b@h JOIN #freeq") |
| 85 | check app.rooms["#freeq"].users.hasKey("bob") |
| 86 | |
| 87 | test "NAMES arrives over several lines and lands in one go": |
| 88 | joined("#freeq") |
| 89 | say(":server 353 alice = #freeq :alice @bob", |
| 90 | ":server 353 alice = #freeq :carol") |
| 91 | # Still pending: replacing the list per line empties the panel and |
| 92 | # refills it a name at a time. |
| 93 | check app.rooms["#freeq"].users.len == 0 |
| 94 | say(":server 366 alice #freeq :End of /NAMES list") |
| 95 | check app.rooms["#freeq"].users.len == 3 |
| 96 | |
| 97 | test "a replayed line lands in the room it names": |
| 98 | joined("#freeq") |
| 99 | say(":server 366 alice #freeq :End of /NAMES list", |
| 100 | ":bob!b@h PRIVMSG #freeq :an old line") |
| 101 | check app.rooms["#freeq"].messages.anyIt(it.text == "an old line") |
| 102 | |
| 103 | test "a message to us is filed under whoever sent it": |
| 104 | say(":bob!b@h PRIVMSG alice :a direct word") |
| 105 | check app.rooms.hasKey("bob") |
| 106 | check app.rooms["bob"].messages[^1].text == "a direct word" |