22
33require "net/imap"
44require "test/unit"
5+ require_relative "fake_server"
56
67class IMAPTest < Test ::Unit ::TestCase
78 CA_FILE = File . expand_path ( "../fixtures/cacert.pem" , __dir__ )
@@ -775,72 +776,28 @@ def test_id
775776 end
776777 end
777778
778- def test_uid_expunge
779- server = create_tcp_server
780- port = server . addr [ 1 ]
781- requests = [ ]
782- start_server do
783- sock = server . accept
784- begin
785- sock . print ( "* OK test server\r \n " )
786- requests . push ( sock . gets )
787- sock . print ( "* 1 EXPUNGE\r \n " )
788- sock . print ( "* 1 EXPUNGE\r \n " )
789- sock . print ( "* 1 EXPUNGE\r \n " )
790- sock . print ( "RUBY0001 OK UID EXPUNGE completed\r \n " )
791- sock . gets
792- sock . print ( "* BYE terminating connection\r \n " )
793- sock . print ( "RUBY0002 OK LOGOUT completed\r \n " )
794- ensure
795- sock . close
796- server . close
779+ def test_uidplus_uid_expunge
780+ with_fake_server ( select : "INBOX" ,
781+ extensions : %i[ UIDPLUS ] ) do |server , imap |
782+ server . on "UID EXPUNGE" do |resp |
783+ resp . untagged ( "1 EXPUNGE" )
784+ resp . untagged ( "1 EXPUNGE" )
785+ resp . untagged ( "1 EXPUNGE" )
786+ resp . done_ok
797787 end
798- end
799-
800- begin
801- imap = Net ::IMAP . new ( server_addr , :port => port )
802788 response = imap . uid_expunge ( 1000 ..1003 )
803- assert_equal ( "RUBY0001 UID EXPUNGE 1000:1003\r \n " , requests . pop )
789+ cmd = server . commands . pop
790+ assert_equal [ "UID EXPUNGE" , "1000:1003" ] , [ cmd . name , cmd . args ]
804791 assert_equal ( response , [ 1 , 1 , 1 ] )
805- imap . logout
806- ensure
807- imap . disconnect if imap
808792 end
809793 end
810794
811- def test_uidplus_responses
812- server = create_tcp_server
813- port = server . addr [ 1 ]
814- requests = [ ]
815- start_server do
816- sock = server . accept
817- begin
818- sock . print ( "* OK test server\r \n " )
819- line = sock . gets
820- size = line . slice ( /{(\d +)}\r \n / , 1 ) . to_i
821- sock . print ( "+ Ready for literal data\r \n " )
822- sock . read ( size )
823- sock . gets
824- sock . print ( "RUBY0001 OK [APPENDUID 38505 3955] APPEND completed\r \n " )
825- requests . push ( sock . gets )
826- sock . print ( "RUBY0002 OK [COPYUID 38505 3955,3960:3962 3963:3966] " \
827- "COPY completed\r \n " )
828- requests . push ( sock . gets )
829- sock . print ( "RUBY0003 OK [COPYUID 38505 3955 3967] COPY completed\r \n " )
830- sock . gets
831- sock . print ( "* NO [UIDNOTSTICKY] Non-persistent UIDs\r \n " )
832- sock . print ( "RUBY0004 OK SELECT completed\r \n " )
833- sock . gets
834- sock . print ( "* BYE terminating connection\r \n " )
835- sock . print ( "RUBY0005 OK LOGOUT completed\r \n " )
836- ensure
837- sock . close
838- server . close
795+ def test_uidplus_appenduid
796+ with_fake_server ( select : "INBOX" ,
797+ extensions : %i[ UIDPLUS ] ) do |server , imap |
798+ server . on "APPEND" do |cmd |
799+ cmd . done_ok code : "APPENDUID 38505 3955"
839800 end
840- end
841-
842- begin
843- imap = Net ::IMAP . new ( server_addr , :port => port )
844801 resp = imap . append ( "inbox" , <<~EOF . gsub ( /\n / , "\r \n " ) , [ :Seen ] , Time . now )
845802 Subject: hello
846803@@ -849,120 +806,76 @@ def test_uidplus_responses
849806 hello world
850807 EOF
851808 assert_equal ( [ 38505 , nil , [ 3955 ] ] , resp . data . code . data . to_a )
809+ assert_equal "APPEND" , server . commands . pop . name
810+ end
811+ end
812+
813+ def test_uidplus_copyuid_multiple
814+ with_fake_server ( select : "INBOX" ,
815+ extensions : %i[ UIDPLUS ] ) do |server , imap |
816+ server . on "UID COPY" do |cmd |
817+ cmd . done_ok code : "COPYUID 38505 3955,3960:3962 3963:3966"
818+ end
852819 resp = imap . uid_copy ( [ 3955 , 3960 ..3962 ] , 'trash' )
853- assert_equal ( requests . pop , "RUBY0002 UID COPY 3955,3960:3962 trash\r \n " )
820+ cmd = server . commands . pop
821+ assert_equal ( [ "UID COPY" , "3955,3960:3962 trash" ] , [ cmd . name , cmd . args ] )
854822 assert_equal (
855823 [ 38505 , [ 3955 , 3960 , 3961 , 3962 ] , [ 3963 , 3964 , 3965 , 3966 ] ] ,
856824 resp . data . code . data . to_a
857825 )
826+ end
827+ end
828+
829+ def test_uidplus_copyuid_single
830+ with_fake_server ( select : "INBOX" ,
831+ extensions : %i[ UIDPLUS ] ) do |server , imap |
832+ server . on "UID COPY" do |cmd |
833+ cmd . done_ok code : "COPYUID 38505 3955 3967"
834+ end
858835 resp = imap . uid_copy ( 3955 , 'trash' )
859- assert_equal ( requests . pop , "RUBY0003 UID COPY 3955 trash\r \n " )
836+ cmd = server . commands . pop
837+ assert_equal ( [ "UID COPY" , "3955 trash" ] , [ cmd . name , cmd . args ] )
860838 assert_equal ( [ 38505 , [ 3955 ] , [ 3967 ] ] , resp . data . code . data . to_a )
839+ end
840+ end
841+
842+ def test_uidplus_uidnotsticky
843+ with_fake_server ( extensions : %i[ UIDPLUS ] ) do |server , imap |
844+ server . config . mailboxes [ "trash" ] = { uidnotsticky : true }
861845 imap . select ( 'trash' )
862- assert_equal (
863- imap . responses ( "NO" , &:last ) . code ,
864- Net ::IMAP ::ResponseCode . new ( 'UIDNOTSTICKY' , nil )
865- )
866- imap . logout
867- ensure
868- imap . disconnect if imap
846+ assert imap . responses ( "NO" , &:to_a ) . any? {
847+ _1 . code == Net ::IMAP ::ResponseCode . new ( 'UIDNOTSTICKY' , nil )
848+ }
869849 end
870850 end
871851
872852 def test_enable
873- requests = Queue . new
874- port = yields_in_test_server_thread do |sock |
875- requests << ( tag , = sock . getcmd ) . join ( " " ) + "\r \n "
876- sock . print "* ENABLED SMTPUTF8\r \n "
877- sock . print "#{ tag } OK \r \n "
878- requests << ( tag , = sock . getcmd ) . join ( " " ) + "\r \n "
879- sock . print "* ENABLED CONDSTORE UTF8=ACCEPT\r \n "
880- sock . print "#{ tag } OK \r \n "
881- requests << ( tag , = sock . getcmd ) . join ( " " ) + "\r \n "
882- sock . print "* ENABLED \r \n "
883- sock . print "#{ tag } OK \r \n "
884- sock . getcmd # waits for logout command
885- end
853+ with_fake_server (
854+ with_extensions : %i[ ENABLE CONDSTORE UTF8=ACCEPT ] ,
855+ capabilities_enablable : %w[ CONDSTORE UTF8=ACCEPT ]
856+ ) do |server , imap |
857+ cmdq = server . commands
886858
887- begin
888- imap = Net ::IMAP . new ( server_addr , port : port )
889- response = imap . enable ( [ "SMTPUTF8" , "X-NO-SUCH-THING" ] )
890- assert_equal ( "RUBY0001 ENABLE SMTPUTF8 X-NO-SUCH-THING\r \n " , requests . pop )
891- assert_equal ( response , [ "SMTPUTF8" ] )
892- response = imap . enable ( :utf8 , "condstore QResync" , "x-pig-latin" )
893- assert_equal ( "RUBY0002 ENABLE UTF8=ACCEPT condstore QResync x-pig-latin\r \n " ,
894- requests . pop )
895- response = imap . enable ( :utf8 , "UTF8=ACCEPT" , "UTF8=ONLY" )
896- assert_equal ( response , [ ] )
897- assert_equal ( "RUBY0003 ENABLE UTF8=ACCEPT\r \n " ,
898- requests . pop )
899- imap . logout
900- ensure
901- imap . disconnect if imap
902- end
903- end
859+ result1 = imap . enable ( %w[ CONDSTORE x-pig-latin ] )
860+ result2 = imap . enable ( :utf8 , "condstore QResync" )
861+ result3 = imap . enable ( :utf8 , "UTF8=ACCEPT" , "UTF8=ONLY" )
862+ cmd1 , cmd2 , cmd3 = Array . new ( 3 ) { cmdq . pop . raw . strip }
904863
905- def yields_in_test_server_thread (
906- read_timeout : 2 , # requires ruby 3.2+
907- timeout : 10 ,
908- greeting : "* OK [CAPABILITY IMAP4rev1 AUTH=PLAIN STARTTLS] test server\r \n "
909- )
910- server = create_tcp_server
911- port = server . addr [ 1 ]
912- last_tag , last_cmd , last_args = nil
913- @threads << Thread . start do
914- Timeout . timeout ( timeout ) do
915- sock = server . accept
916- sock . timeout = read_timeout if sock . respond_to? :timeout # ruby 3.2+
917- sock . singleton_class . define_method ( :getcmd ) do
918- buf = "" . b
919- buf << ( sock . gets || "" ) until /\A ([^ ]+) ([^ ]+) ?(.*)\r \n \z /mn =~ buf
920- [ last_tag = $1, last_cmd = $2, last_args = $3]
921- end
922- begin
923- sock . print ( greeting )
924- yield sock
925- ensure
926- begin
927- sock . print ( "* BYE terminating connection\r \n " )
928- last_cmd =~ /LOGOUT/i and
929- sock . print ( "#{ last_tag } OK LOGOUT completed\r \n " )
930- ensure
931- sock . close
932- server . close
933- end
934- end
935- end
864+ assert_equal "RUBY0001 ENABLE CONDSTORE x-pig-latin" , cmd1
865+ assert_equal "RUBY0002 ENABLE UTF8=ACCEPT condstore QResync" , cmd2
866+ assert_equal "RUBY0003 ENABLE UTF8=ACCEPT" , cmd3
867+ assert_empty cmdq
868+
869+ assert_equal %w[ CONDSTORE ] , result1
870+ assert_equal %w[ UTF8=ACCEPT ] , result2
871+ assert_equal [ ] , result3
936872 end
937- port
938873 end
939874
940- # SELECT returns many different untagged results, so this is useful for
941- # several different tests.
942- RFC3501_6_3_1_SELECT_EXAMPLE_DATA = <<~RESPONSES
943- * 172 EXISTS
944- * 1 RECENT
945- * OK [UNSEEN 12] Message 12 is first unseen
946- * OK [UIDVALIDITY 3857529045] UIDs valid
947- * OK [UIDNEXT 4392] Predicted next UID
948- * FLAGS (\\ Answered \\ Flagged \\ Deleted \\ Seen \\ Draft)
949- * OK [PERMANENTFLAGS (\\ Deleted \\ Seen \\ *)] Limited
950- %{tag} OK [READ-WRITE] SELECT completed
951- RESPONSES
952- . split ( "\n " ) . join ( "\r \n " ) . concat ( "\r \n " ) . freeze
953-
954875 def test_responses
955- port = yields_in_test_server_thread do |sock |
956- tag , name , = sock . getcmd
957- if name == "SELECT"
958- sock . print RFC3501_6_3_1_SELECT_EXAMPLE_DATA % { tag : tag }
959- end
960- sock . getcmd # waits for logout command
961- end
962- begin
963- imap = Net ::IMAP . new ( server_addr , port : port )
876+ with_fake_server do |server , imap |
964877 # responses available before SELECT/EXAMINE
965- assert_equal ( %w[ IMAP4REV1 AUTH=PLAIN STARTTLS ] ,
878+ assert_equal ( %w[ IMAP4REV1 NAMESPACE MOVE IDLE UTF8=ACCEPT ] ,
966879 imap . responses ( "CAPABILITY" , &:last ) )
967880 resp = imap . select "INBOX"
968881 # responses are cleared after SELECT/EXAMINE
@@ -978,22 +891,11 @@ def test_responses
978891 # assert_equal(%i[Answered Flagged Deleted Seen Draft],
979892 # imap.responses["FLAGS"]&.last)
980893 # end
981- imap . logout
982- ensure
983- imap . disconnect if imap
984894 end
985895 end
986896
987897 def test_clear_responses
988- port = yields_in_test_server_thread do |sock |
989- tag , name , = sock . getcmd
990- if name == "SELECT"
991- sock . print RFC3501_6_3_1_SELECT_EXAMPLE_DATA % { tag : tag }
992- end
993- sock . getcmd # waits for logout command
994- end
995- begin
996- imap = Net ::IMAP . new ( server_addr , port : port )
898+ with_fake_server do |server , imap |
997899 resp = imap . select "INBOX"
998900 assert_equal ( [ Net ::IMAP ::TaggedResponse , "RUBY0001" , "OK" ] ,
999901 [ resp . class , resp . tag , resp . name ] )
@@ -1013,54 +915,55 @@ def test_clear_responses
1013915 assert_equal ( 3 , responses [ "PERMANENTFLAGS" ] . last &.size )
1014916 assert_equal ( { } , imap . responses ( &:itself ) )
1015917 assert_equal ( { } , imap . clear_responses )
1016- imap . logout
1017- ensure
1018- imap . disconnect if imap
1019918 end
1020919 end
1021920
1022921 def test_close
1023- requests = Queue . new
1024- port = yields_in_test_server_thread do |sock |
1025- requests << sock . getcmd
1026- sock . print ( "RUBY0001 OK CLOSE completed\r \n " )
1027- requests << sock . getcmd
1028- end
1029- begin
1030- imap = Net ::IMAP . new ( server_addr , :port => port )
922+ with_fake_server ( select : "inbox" ) do |server , imap |
1031923 resp = imap . close
1032- assert_equal ( [ "RUBY0001" , " CLOSE", "" ] , requests . pop )
1033- assert_equal ( [ Net ::IMAP ::TaggedResponse , "RUBY0001 " , "OK" ] ,
924+ assert_equal ( "RUBY0002 CLOSE", server . commands . pop . raw . strip )
925+ assert_equal ( [ Net ::IMAP ::TaggedResponse , "RUBY0002 " , "OK" ] ,
1034926 [ resp . class , resp . tag , resp . name ] )
1035- imap . logout
1036- assert_equal ( [ "RUBY0002" , "LOGOUT" , "" ] , requests . pop )
1037- ensure
1038- imap . disconnect if imap
927+ assert_empty server . commands
1039928 end
1040929 end
1041930
1042931 def test_unselect
1043- requests = Queue . new
1044- port = yields_in_test_server_thread do |sock |
1045- requests << sock . getcmd
1046- sock . print ( "RUBY0001 OK UNSELECT completed\r \n " )
1047- requests << sock . getcmd
1048- end
1049- begin
1050- imap = Net ::IMAP . new ( server_addr , port : port )
932+ with_fake_server ( select : "inbox" ) do |server , imap |
1051933 resp = imap . unselect
1052- assert_equal ( [ "RUBY0001" , "UNSELECT" , "" ] , requests . pop )
1053- assert_equal ( [ Net ::IMAP ::TaggedResponse , "RUBY0001" , "OK" ] ,
934+ sent = server . commands . pop
935+ assert_equal ( [ "UNSELECT" , nil ] , [ sent . name , sent . args ] )
936+ assert_equal ( [ Net ::IMAP ::TaggedResponse , "RUBY0002" , "OK" ] ,
1054937 [ resp . class , resp . tag , resp . name ] )
1055- imap . logout
1056- assert_equal ( [ "RUBY0002" , "LOGOUT" , "" ] , requests . pop )
1057- ensure
1058- imap . disconnect if imap
938+ assert_empty server . commands
1059939 end
1060940 end
1061941
1062942 private
1063943
944+ def with_fake_server ( select : nil , timeout : 5 , **opts )
945+ Timeout . timeout ( timeout ) do
946+ server = Net ::IMAP ::FakeServer . new ( timeout : timeout , **opts )
947+ @threads << Thread . new do server . run end
948+ tls = opts [ :implicit_tls ]
949+ tls = { ca_file : server . config . tls [ :ca_file ] } if tls == true
950+ client = Net ::IMAP . new ( "localhost" , port : server . port , ssl : tls )
951+ begin
952+ if select
953+ client . select ( select )
954+ server . commands . pop
955+ assert server . state . selected?
956+ end
957+ yield server , client
958+ ensure
959+ client . logout rescue pp $!
960+ client . disconnect if !client . disconnected?
961+ end
962+ ensure
963+ server &.shutdown
964+ end
965+ end
966+
1064967 def imaps_test
1065968 server = create_tcp_server
1066969 port = server . addr [ 1 ]
0 commit comments