2
2
3
3
require "net/imap"
4
4
require "test/unit"
5
+ require_relative "mock_server"
5
6
6
7
class IMAPTest < Test ::Unit ::TestCase
7
8
CA_FILE = File . expand_path ( "../fixtures/cacert.pem" , __dir__ )
@@ -775,72 +776,28 @@ def test_id
775
776
end
776
777
end
777
778
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_mock_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
797
787
end
798
- end
799
-
800
- begin
801
- imap = Net ::IMAP . new ( server_addr , :port => port )
802
788
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 ]
804
791
assert_equal ( response , [ 1 , 1 , 1 ] )
805
- imap . logout
806
- ensure
807
- imap . disconnect if imap
808
792
end
809
793
end
810
794
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_mock_server ( select : "INBOX" ,
797
+ extensions : %i[ UIDPLUS ] ) do |server , imap |
798
+ server . on "APPEND" do |cmd |
799
+ cmd . done_ok code : "APPENDUID 38505 3955"
839
800
end
840
- end
841
-
842
- begin
843
- imap = Net ::IMAP . new ( server_addr , :port => port )
844
801
resp = imap . append ( "inbox" , <<~EOF . gsub ( /\n / , "\r \n " ) , [ :Seen ] , Time . now )
845
802
Subject: hello
846
803
@@ -849,120 +806,76 @@ def test_uidplus_responses
849
806
hello world
850
807
EOF
851
808
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_mock_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
852
819
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 ] )
854
822
assert_equal (
855
823
[ 38505 , [ 3955 , 3960 , 3961 , 3962 ] , [ 3963 , 3964 , 3965 , 3966 ] ] ,
856
824
resp . data . code . data . to_a
857
825
)
826
+ end
827
+ end
828
+
829
+ def test_uidplus_copyuid_single
830
+ with_mock_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
858
835
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 ] )
860
838
assert_equal ( [ 38505 , [ 3955 ] , [ 3967 ] ] , resp . data . code . data . to_a )
839
+ end
840
+ end
841
+
842
+ def test_uidplus_uidnotsticky
843
+ with_mock_server ( extensions : %i[ UIDPLUS ] ) do |server , imap |
844
+ server . config . mailboxes [ "trash" ] = { uidnotsticky : true }
861
845
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
+ }
869
849
end
870
850
end
871
851
872
852
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_mock_server (
854
+ with_extensions : %i[ ENABLE CONDSTORE UTF8=ACCEPT ] ,
855
+ capabilities_enablable : %w[ CONDSTORE UTF8=ACCEPT ]
856
+ ) do |server , imap |
857
+ cmdq = server . commands
886
858
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 }
904
863
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
936
872
end
937
- port
938
873
end
939
874
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
-
954
875
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_mock_server do |server , imap |
964
877
# responses available before SELECT/EXAMINE
965
- assert_equal ( %w[ IMAP4REV1 AUTH=PLAIN STARTTLS ] ,
878
+ assert_equal ( %w[ IMAP4REV1 NAMESPACE MOVE IDLE UTF8=ACCEPT ] ,
966
879
imap . responses ( "CAPABILITY" , &:last ) )
967
880
resp = imap . select "INBOX"
968
881
# responses are cleared after SELECT/EXAMINE
@@ -978,22 +891,11 @@ def test_responses
978
891
# assert_equal(%i[Answered Flagged Deleted Seen Draft],
979
892
# imap.responses["FLAGS"]&.last)
980
893
# end
981
- imap . logout
982
- ensure
983
- imap . disconnect if imap
984
894
end
985
895
end
986
896
987
897
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_mock_server do |server , imap |
997
899
resp = imap . select "INBOX"
998
900
assert_equal ( [ Net ::IMAP ::TaggedResponse , "RUBY0001" , "OK" ] ,
999
901
[ resp . class , resp . tag , resp . name ] )
@@ -1013,54 +915,55 @@ def test_clear_responses
1013
915
assert_equal ( 3 , responses [ "PERMANENTFLAGS" ] . last &.size )
1014
916
assert_equal ( { } , imap . responses ( &:itself ) )
1015
917
assert_equal ( { } , imap . clear_responses )
1016
- imap . logout
1017
- ensure
1018
- imap . disconnect if imap
1019
918
end
1020
919
end
1021
920
1022
921
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_mock_server ( select : "inbox" ) do |server , imap |
1031
923
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" ] ,
1034
926
[ 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
1039
928
end
1040
929
end
1041
930
1042
931
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_mock_server ( select : "inbox" ) do |server , imap |
1051
933
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" ] ,
1054
937
[ 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
1059
939
end
1060
940
end
1061
941
1062
942
private
1063
943
944
+ def with_mock_server ( select : nil , timeout : 5 , **opts )
945
+ Timeout . timeout ( timeout ) do
946
+ server = Net ::IMAP ::MockServer . 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
+
1064
967
def imaps_test
1065
968
server = create_tcp_server
1066
969
port = server . addr [ 1 ]
0 commit comments