4
0

eggdrop-pisg-test.tcl 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. # Tests for eggdrop-pisg.tcl, run outside eggdrop with a mock of the eggdrop commands:
  2. # tclsh8.6 scripts/eggdrop-pisg-test.tcl
  3. # They never touch a real users file: everything happens in a temporary folder.
  4. set here [file dirname [file normalize [info script]]]
  5. set tmp [file join [expr {[info exists ::env(TMPDIR)] ? $::env(TMPDIR) : "/tmp"}] "pisgtest.[pid]"]
  6. file mkdir $tmp
  7. # ---- a mock of the eggdrop commands the script uses ---------------------------------------
  8. set ::sent {}; set ::binds {}; set ::logs {}; set ::acctreply {}
  9. proc bind {type flags cmd proc} { lappend ::binds [list $type $flags $cmd $proc] }
  10. proc putserv {line} { lappend ::sent $line }
  11. proc puthelp {line} { lappend ::sent $line }
  12. proc putlog {line} { lappend ::logs $line }
  13. proc timer {args} {}
  14. proc matchattr {hand flags} { return [expr {$hand eq "boss"}] }
  15. proc getaccount {nick} { if {[dict exists $::acctreply $nick]} { return [dict get $::acctreply $nick] } else { return "" } }
  16. source [file join $here eggdrop-pisg.tcl]
  17. set pisg::usersfile [file join $tmp users.cfg]
  18. set pisg::maincfg [file join $tmp pisg.cfg]
  19. set pisg::minsecs 0
  20. set fails 0; set passes 0
  21. proc ok {cond msg} { if {$cond} { incr ::passes } else { incr ::fails; puts " FAIL: $msg" } }
  22. # run a command as if typed in a private message; returns what the bot said (NOTICE texts)
  23. proc say {cmd nick uhost hand arg} {
  24. set ::sent {}
  25. pisg::dispatch $cmd $nick $uhost $hand $arg
  26. set out {}
  27. foreach l $::sent { regexp {^NOTICE \S+ :(.*)$} $l -> t; lappend out $t }
  28. return $out
  29. }
  30. proc first {lines} { return [lindex $lines 0] }
  31. proc filetext {} { return [pisg::read_file $pisg::usersfile] }
  32. proc reset {} { file delete -force $pisg::usersfile; file delete -force $pisg::maincfg; array unset pisg::hist; set ::acctreply {} }
  33. set ALICE "~u@alice.users.undernet.org"
  34. set BOB "~b@bob.users.undernet.org"
  35. set ANON "~x@dsl-123.example.net"
  36. puts "1. identity"
  37. reset
  38. ok [string match "*Log in to X*" [first [say info Alice $ANON * "Alice male"]]] "someone not logged in to X gets login instructions"
  39. ok [expr {[pisg::account Alice $ALICE] eq "alice"}] "account name from the host"
  40. ok [expr {[pisg::account Alice $ANON] eq ""}] "no account for an ordinary host"
  41. set ::acctreply [dict create Alice "AliceAcct"]
  42. ok [expr {[pisg::account Alice $ANON] eq "aliceacct"}] "getaccount is used first, lower-cased"
  43. set ::acctreply [dict create Alice "*"]
  44. ok [expr {[pisg::account Alice $ALICE] eq "alice"}] "getaccount '*' (not logged in) falls back to the host"
  45. set ::acctreply {}
  46. ok [string match "*Log in to X*" [first [say info Alice $ANON * "Alice male"]]] "still no login: still no edit"
  47. ok [expr {![file exists $pisg::usersfile]}] "nothing written for an unidentified user"
  48. puts "2. creating and updating a profile"
  49. reset
  50. set r [first [say info Alice $ALICE * "Alice male https://example.org/me.png https://example.org"]]
  51. ok [string match "Saved*" $r] "profile saved: $r"
  52. set t [filetext]
  53. ok [string match {*<user nick="Alice" pic="https://example.org/me.png" sex="m" link="https://example.org" owner="alice">*} $t] "file line is right: $t"
  54. ok [string match "*do not edit below this line*" $t] "marker written"
  55. set r [first [say info Alice $ALICE * "Alice female"]]
  56. set t [filetext]
  57. ok [string match {*sex="f"*} $t] "sex updated"
  58. ok [string match {*pic="https://example.org/me.png"*} $t] "picture kept when only sex changes"
  59. ok [string match "*female*" $r] "reply describes the profile"
  60. say info Alice $ALICE * "Alice link=mailto-not https://new.example.org/x" ;# an invalid token must change nothing
  61. ok [string match {*link="https://example.org"*} [filetext]] "invalid input changes nothing"
  62. ok [string match "*don't understand*" [first [say info Alice $ALICE * "Alice blah"]]] "unknown word explained"
  63. puts "3. claiming names"
  64. reset
  65. ok [string match "*only create a profile for the nick you are using*" [first [say info Bob $BOB * "Alice male"]]] "cannot create a profile for a nick you are not using"
  66. say info Alice $ALICE * "Alice male"
  67. ok [string match "*already taken*" [first [say info Alice $BOB * "Alice male"]]] "cannot take a name somebody has"
  68. ok [string match "*don't have a profile called Alice*" [first [say merge Bob $BOB * "Alice"]]] "cannot merge into somebody else's profile"
  69. say info Bob $BOB * "Bob male"
  70. ok [string match "*already have a profile named Bob*" [first [say info Bob $BOB * "Zed male"]]] "one profile per account"
  71. ok [string match "*already have a profile named Bob*" [first [say info Bob2 $BOB * "Bob2 male"]]] "still one profile per account"
  72. puts "4. merging nicks"
  73. reset
  74. say info Alice $ALICE * "Alice male"
  75. ok [string match "Done*" [first [say merge Alice_ $ALICE * "Alice"]]] "Alice_ merged into Alice"
  76. ok [string match {*alias="Alice_"*} [filetext]] "alias written"
  77. ok [string match "*already counted*" [first [say merge Alice_ $ALICE * "Alice"]]] "merging twice is harmless"
  78. ok [string match "*already Alice*" [first [say merge Alice $ALICE * "Alice"]]] "merging the main nick into itself is refused"
  79. say info Bob $BOB * "Bob male"
  80. ok [string match "*already used by another profile*" [first [say merge Alice_ $BOB * "Bob"]]] "an alias held by someone else cannot be taken"
  81. say merge Alice- $ALICE * "Alice"
  82. ok [string match {*alias="Alice_ Alice-"*} [filetext]] "second alias appended"
  83. ok [string match "Done*" [first [say unmerge Alice $ALICE * "Alice_"]]] "unmerge works"
  84. ok [string match {*alias="Alice-"*} [filetext]] "alias removed"
  85. ok [string match "*not one of your aliases*" [first [say unmerge Alice $ALICE * "Nobody"]]] "unmerge of a stranger refused"
  86. puts "5. your hand-written lines are protected and preserved"
  87. reset
  88. set f [open $pisg::maincfg w]; puts $f "# main\n<user nick=\"Zed\" alias=\"Zeddy Z-\" sex=\"m\">"; close $f
  89. set f [open $pisg::usersfile w]; puts $f "# my notes\n<user nick=\"Manny\" pic=\"a.png\">"; close $f
  90. ok [string match "*already taken*" [first [say info Zed "~z@zed.users.undernet.org" * "Zed male"]]] "a nick from pisg.cfg cannot be claimed"
  91. ok [string match "*already taken*" [first [say info Zeddy "~z@zeddy.users.undernet.org" * "Zeddy male"]]] "an alias from pisg.cfg cannot be claimed"
  92. ok [string match "*already taken*" [first [say info Manny "~m@manny.users.undernet.org" * "Manny male"]]] "a hand-written line in users.cfg cannot be claimed"
  93. say info Alice $ALICE * "Alice male"; say info Bob $BOB * "Bob male"
  94. set t [filetext]
  95. ok [string match "*# my notes*" $t] "your comment survives"
  96. ok [string match {*<user nick="Manny" pic="a.png">*} $t] "your <user> line survives untouched"
  97. ok [expr {[string first "# my notes" $t] < [string first "managed by" $t]}] "your part stays above the marker"
  98. ok [expr {[string first {nick="Alice"} $t] < [string first {nick="Bob"} $t]}] "managed entries are sorted"
  99. ok [string match "*set by hand*" [first [say show Alice $ALICE * "Zed"]]] "show says a hand-written profile exists"
  100. puts "6. hostile and invalid input"
  101. reset
  102. foreach bad {
  103. "Alice male http://example.org/me.png" "Alice male javascript:alert(1)"
  104. "Alice male https://localhost/me.png" "Alice male https://127.0.0.1/me.png"
  105. "Alice male https://example.org/a\"b.png" "Alice male https://example.org/me.png?x=<script>"
  106. "Alice male https://exa mple.org/me.png" "Alice male https://example..org/me.png"
  107. "Se<b male" "Alice sex=alien"
  108. "Alice male https://.org/x.png" } {
  109. set r [say info Alice $ALICE * $bad]
  110. ok [expr {![string match "Saved*" [first $r]]}] "refused: $bad"
  111. }
  112. ok [expr {![file exists $pisg::usersfile]}] "nothing written by any of them"
  113. ok [pisg::valid_url "https://example.org/a/b?c=d"] "a normal https link is fine"
  114. ok [expr {![pisg::valid_url "https://example.org/[string repeat a 300]"]}] "over-long links refused"
  115. ok [pisg::valid_email "me@example.org"] "e-mail is a valid link"
  116. ok [expr {![pisg::valid_email "me@example.org\"><b>"]}] "hostile e-mail refused"
  117. set r [first [say info Alice $ALICE * "Alice me@example.org"]]
  118. ok [string match "Saved*" $r] "an e-mail address works as the link"
  119. puts "7. nicks with characters that are special in Tcl lists"
  120. reset
  121. set ODD "~o@odd.users.undernet.org"
  122. ok [string match "Saved*" [first [say info {[x]{y}} $ODD * {[x]{y} male}]]] "profile for a nick with braces and brackets"
  123. ok [string match "Done*" [first [say merge {a\b} $ODD * {[x]{y}}]]] "alias with a backslash"
  124. ok [string match "Done*" [first [say merge {c|d^e} $ODD * {[x]{y}}]]] "alias with | and ^"
  125. lassign [pisg::load] man ents
  126. ok [expr {[llength $ents] == 1}] "one entry read back"
  127. ok [expr {[dict get [lindex $ents 0] name] eq "{\[x\]{y}}" || [dict get [lindex $ents 0] name] eq {[x]{y}}}] "name read back exactly"
  128. ok [expr {[dict get [lindex $ents 0] aliases] eq [list {a\b} {c|d^e}]}] "aliases read back exactly: [dict get [lindex $ents 0] aliases]"
  129. set again [say show {a\b} $ODD * ""]
  130. ok [expr {[string first {[x]{y}} [first $again]] >= 0 && [string first {a\b} [first $again]] >= 0}] "show works and prints the special characters literally: [first $again]"
  131. puts "8. show, delete, delete-anybody"
  132. reset
  133. say info Alice $ALICE * "Alice male https://example.org/me.png"
  134. ok [string match "*Alice: male*" [first [say show Bob $BOB * "Alice"]]] "anyone can look a profile up"
  135. ok [string match "*No profile*" [first [say show Bob $BOB * "Nobody"]]] "unknown profile"
  136. ok [string match "*no profile yet*" [first [say show Bob $BOB * ""]]] "no argument, no profile: says so"
  137. ok [string match "*Alice: male*" [first [say show Alice $ALICE * ""]]] "no argument shows your own"
  138. ok [string match "*You don't have a profile*" [first [say del Bob $BOB * ""]]] "delete without a profile"
  139. ok [string match "*Only bot masters*" [first [say deluser Bob $BOB * "Alice"]]] "a normal user cannot delete somebody else's"
  140. ok [string match "Deleted*" [first [say deluser Boss "~b@boss.users.undernet.org" boss "Alice"]]] "a bot master can"
  141. ok [expr {![string match {*nick="Alice"*} [filetext]]}] "gone from the file"
  142. say info Alice $ALICE * "Alice male"
  143. ok [string match "Deleted*" [first [say del Alice $ALICE * ""]]] "you can delete your own"
  144. ok [expr {![string match {*nick="Alice"*} [filetext]]}] "gone from the file"
  145. set f [open $pisg::maincfg w]; puts $f "<user nick=\"Zed\">"; close $f
  146. ok [string match "*written by hand*" [first [say deluser Boss "~b@boss.users.undernet.org" boss "Zed"]]] "a bot master cannot delete a hand-written entry"
  147. puts "9. help"
  148. reset
  149. set r [say help Alice $ALICE * ""]
  150. ok [string match "*pisginfo*pisgmerge*pisgshow*" [first $r]] "lists the commands"
  151. ok [string match "*!pisghelp pisgshow*" [first $r]] "gives the !pisghelp <command> example"
  152. foreach c {pisginfo pisgmerge pisgunmerge pisgshow pisgdel pisgdeluser pisgstats pisghelp} {
  153. ok [string match "*$c*" [first [say help Alice $ALICE * $c]]] "help for $c"
  154. }
  155. ok [string match "*pisgshow*" [first [say help Alice $ALICE * "!pisgshow"]]] "!pisgshow accepted"
  156. ok [string match "*pisgshow*" [first [say help Alice $ALICE * "show"]]] "'show' short form accepted"
  157. ok [string match "*don't know a command*" [first [say help Alice $ALICE * "nonsense"]]] "unknown command"
  158. puts "10. rate limits"
  159. reset
  160. set pisg::minsecs 5
  161. say info Alice $ALICE * "Alice male"
  162. ok [string match "*Slow down*" [first [say info Alice $ALICE * "Alice female"]]] "two changes in a row are slowed"
  163. ok [string match "*Alice*" [first [say show Alice $ALICE * ""]]] "reading is never slowed"
  164. set pisg::minsecs 0; set pisg::maxperhour 3; array unset pisg::hist
  165. for {set i 0} {$i < 3} {incr i} { say info Alice $ALICE * "Alice male" }
  166. ok [string match "*Slow down*" [first [say info Alice $ALICE * "Alice female"]]] "too many changes in an hour are refused"
  167. ok [string match "*Alice*" [first [say show Alice $ALICE * ""]]] "still can read"
  168. set pisg::maxperhour 30
  169. puts "11. replies stay short enough for IRC, and carry no control codes"
  170. reset
  171. set long "https://example.org/[string repeat p 250].png"
  172. say info Alice $ALICE * "Alice male $long https://example.org/[string repeat q 250]"
  173. foreach l [say show Alice $ALICE * ""] { ok [expr {[string length $l] <= 400}] "line is [string length $l] characters" }
  174. ok [expr {[pisg::clean "a\x01b\x02c\r\nd"] eq "a b c d"}] "control codes removed"
  175. puts "12. bindings"
  176. set types {}; foreach b $::binds { lappend types [lrange $b 0 2] }
  177. foreach c {help info merge unmerge show del deluser} {
  178. ok [expr {[lsearch $types "pub - !pisg$c"] >= 0}] "channel command !pisg$c"
  179. ok [expr {[lsearch $types "msg - pisg$c"] >= 0}] "private message pisg$c"
  180. }
  181. ok [expr {[lsearch $types "pub - !pisgstats"] >= 0}] "!pisgstats is open to everyone"
  182. file delete -force $tmp
  183. puts "\n$passes passed, $fails failed"
  184. exit [expr {$fails ? 1 : 0}]