imap 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 587a50234d8535c27ceeace7941c30f74147ef68bb65e835eb84b04878bc91c7
4
+ data.tar.gz: 2778297913ec8a25dcb39e014cff11c538b8eee0bb706846d13045f744995096
5
+ SHA512:
6
+ metadata.gz: fb5a7c2332893c2a2fd9c0728480a6c098302d14a389986c1003cc5b547ec85a9dbff6ab4f7927bfdc5039b45d54719da17a1b1b4e0e88f35a03d741cb096313
7
+ data.tar.gz: 8ac81274dd278a5fd71fa3434837df50c39a73db7e311e9ddfdc15e51e7320992383987a129f321cbda3a6ad8c2a8da55ab02c272a713e2ed8a9ff72f7af0768
data/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # imap
2
+
3
+ A Ruby wrapper around Net::IMAP with an elegant DSL for searching, reading, and managing email.
4
+
5
+ ## Features
6
+
7
+ - Clean, hash-based search interface
8
+ - Intuitive message accessors
9
+ - No Rails dependency - works anywhere
10
+ - Zero external dependencies beyond net-imap
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ ```ruby
17
+ gem 'imap'
18
+ ```
19
+
20
+ And then execute:
21
+
22
+ ```bash
23
+ $ bundle install
24
+ ```
25
+
26
+ Or install it yourself as:
27
+
28
+ ```bash
29
+ $ gem install imap
30
+ ```
31
+
32
+ ## Usage
33
+ ```ruby
34
+ require 'imap'
35
+
36
+ # Connect and authenticate
37
+ imap_client = Imap.setup(
38
+ server: 'imap.thoran.com',
39
+ username: 'code@thoran.com',
40
+ password: 'bigsecret',
41
+ mailbox: 'INBOX' # optional/default
42
+ )
43
+
44
+ messages = imap_client.search(
45
+ from: 'noreply@example.com',
46
+ subject: 'Payday Loans',
47
+ since: '18-DEC-2025',
48
+ seen: false
49
+ )
50
+
51
+ # Access message details
52
+ messages.each do |message|
53
+ puts message.subject
54
+ puts message.from
55
+ puts message.to
56
+ puts message.body
57
+ puts message.urls # Extracted URLs from body
58
+ end
59
+
60
+ # Mark messages as read
61
+ messages.first.mark_as_read
62
+
63
+ # Clean up
64
+ imap_client.bye
65
+ ```
66
+
67
+ ## Contributing
68
+
69
+ 1. Fork it (https://github.com/thoran/imap/fork)
70
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
71
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
72
+ 4. Push to the branch (`git push origin my-new-feature`)
73
+ 5. Create a new pull request
74
+
75
+ ## License
76
+
77
+ MIT
data/imap.gemspec ADDED
@@ -0,0 +1,33 @@
1
+ # imap.gemspec
2
+
3
+ require_relative './lib/Imap/VERSION'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'imap'
7
+
8
+ spec.version = Imap::VERSION
9
+ spec.date = '2025-12-20'
10
+
11
+ spec.summary = "IMAP for Ruby made easy."
12
+ spec.description = "A Ruby wrapper around Net::IMAP with an elegant DSL for searching, reading, and managing email."
13
+
14
+ spec.author = 'thoran'
15
+ spec.email = 'code@thoran.com'
16
+ spec.homepage = 'https://github.com/thoran/imap'
17
+ spec.license = 'MIT'
18
+
19
+ spec.required_ruby_version = '>= 2.7'
20
+
21
+ spec.add_dependency 'net-imap', "~> 0.4"
22
+
23
+ spec.add_development_dependency 'minitest', '~> 5.0'
24
+ spec.add_development_dependency 'rake', '~> 13.0'
25
+
26
+ spec.files = [
27
+ 'imap.gemspec',
28
+ Dir['lib/**/*.rb'],
29
+ 'README.md',
30
+ Dir['test/**/*.rb']
31
+ ].flatten
32
+ spec.require_paths = ['lib']
33
+ end
@@ -0,0 +1,67 @@
1
+ # Imap/Message.rb
2
+ # Imap::Message
3
+
4
+ # Usage:
5
+ # 1. Imap::Message.search(imap_client, {from: 'noreply@example.com'})
6
+ # 2. Imap::Message.new(message_id, imap_client)
7
+ # 3. Imap::Message.new(message_id, imap_client).body
8
+ # 4. Imap::Message.new(message_id, imap_client).urls
9
+ # 5. Imap::Message.new(message_id, imap_client).mark_as_read
10
+ # 6. Imap::Message.new(message_id, imap_client).subject
11
+ # 7. Imap::Message.new(message_id, imap_client).from
12
+ # 8. Imap::Message.new(message_id, imap_client).to
13
+
14
+ require_relative './Search'
15
+
16
+ class Imap
17
+ class Message
18
+ class << self
19
+
20
+ def search(imap_client, **search_criteria)
21
+ message_ids = Imap::Search.new(imap_client, search_criteria).message_ids
22
+ message_ids.collect{|message_id| Imap::Message.new(message_id, imap_client)}
23
+ end
24
+ alias_method :find, :search
25
+
26
+ end # class << self
27
+
28
+ attr_accessor :imap_client
29
+ attr_accessor :message_id
30
+
31
+ def body
32
+ @body ||= fetch_data('BODY[TEXT]').first.attr['BODY[TEXT]']
33
+ end
34
+
35
+ def subject
36
+ @subject ||= fetch_data('BODY[HEADER.FIELDS (SUBJECT)]').first.attr['BODY[HEADER.FIELDS (SUBJECT)]'].sub(/^Subject: /, '').strip
37
+ end
38
+
39
+ def from
40
+ @from ||= fetch_data('BODY[HEADER.FIELDS (FROM)]').first.attr['BODY[HEADER.FIELDS (FROM)]'].sub(/^From: /, '').strip
41
+ end
42
+
43
+ def to
44
+ @to ||= fetch_data('ENVELOPE').first.attr['ENVELOPE'].to.map{|addr| addr.mailbox + '@' + addr.host}
45
+ end
46
+
47
+ def urls
48
+ body.scan(/https?:\/\/[\S]+/)
49
+ end
50
+
51
+ def mark_as_seen
52
+ imap_client.imap.store(@message_id, '+FLAGS', [:Seen])
53
+ end
54
+ alias_method :mark_as_read, :mark_as_seen
55
+
56
+ private
57
+
58
+ def initialize(message_id = nil, imap_client = nil)
59
+ @message_id = message_id
60
+ @imap_client = imap_client
61
+ end
62
+
63
+ def fetch_data(*attrs)
64
+ @fetch_data = imap_client.imap.fetch(message_id, [*attrs])
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,143 @@
1
+ # Imap/Search.rb
2
+ # Imap::Search
3
+
4
+ # Usage:
5
+ # 1. Imap::Search.new.not.subject('Payday Loans').answered.from('noreply@example.com').all
6
+ # 2. Imap::Search.new({from: 'noreply@example.com', answered: true})
7
+
8
+ # Notes:
9
+ # 1. List of search keys taken from RFC-3501 (INTERNET MESSAGE ACCESS PROTOCOL - VERSION 4rev1), http://tools.ietf.org/html/rfc3501.
10
+
11
+ class Imap
12
+ class Search
13
+
14
+ ALL_SEARCH_KEYS = %w{
15
+ ALL
16
+ ANSWERED
17
+ BCC
18
+ BEFORE
19
+ BODY
20
+ CC
21
+ DELETED
22
+ DRAFT
23
+ FLAGGED
24
+ FROM
25
+ HEADER
26
+ KEYWORD
27
+ LARGER
28
+ NEW
29
+ NOT
30
+ OLD
31
+ ON
32
+ OR
33
+ RECENT
34
+ SEEN
35
+ SENTBEFORE
36
+ SENTON
37
+ SENTSINCE
38
+ SINCE
39
+ SMALLER
40
+ SUBJECT
41
+ TEXT
42
+ TO
43
+ UID
44
+ UNANSWERED
45
+ UNDELETED
46
+ UNDRAFT
47
+ UNFLAGGED
48
+ UNKEYWORD
49
+ UNSEEN
50
+ }
51
+
52
+ BOOLEAN_SEARCH_KEYS = %w{
53
+ ANSWERED UNANSWERED
54
+ DELETED UNDELETED
55
+ DRAFT UNDRAFT
56
+ FLAGGED UNFLAGGED
57
+ NEW
58
+ OLD
59
+ RECENT
60
+ SEEN UNSEEN
61
+ }
62
+
63
+ GENERAL_SEARCH_KEYS = %w{
64
+ BCC
65
+ BEFORE
66
+ BODY
67
+ CC
68
+ FROM
69
+ HEADER
70
+ KEYWORD UNKEYWORD
71
+ LARGER
72
+ ON
73
+ SENTBEFORE
74
+ SENTON
75
+ SENTSINCE
76
+ SINCE
77
+ SMALLER
78
+ SUBJECT
79
+ TEXT
80
+ TO
81
+ UID
82
+ }
83
+
84
+ BOOLEAN_SEARCH_KEYS.each do |boolean_search_key|
85
+ define_method boolean_search_key do |value = true|
86
+ criteria.merge!(boolean_search_key.to_sym => value)
87
+ end
88
+ end
89
+
90
+ GENERAL_SEARCH_KEYS.each do |general_search_key|
91
+ define_method general_search_key do |value|
92
+ criteria.merge!(general_search_key.to_sym => value)
93
+ end
94
+ end
95
+
96
+ attr_accessor :criteria
97
+ attr_accessor :imap_client
98
+
99
+ def general_operator?(search_key)
100
+ GENERAL_SEARCH_KEYS.include?(search_key)
101
+ end
102
+
103
+ def boolean_operator?(search_key)
104
+ BOOLEAN_SEARCH_KEYS.include?(search_key)
105
+ end
106
+
107
+ def to_imap_search_keys
108
+ criteria.inject([]) do |m, kv|
109
+ key, value = kv.first.to_s.upcase, kv.last
110
+ case
111
+ when general_operator?(key)
112
+ m << general_to_imap_search_key(key, value)
113
+ when boolean_operator?(key)
114
+ m << boolean_to_imap_search_key(key, value)
115
+ end
116
+ end.flatten
117
+ end
118
+
119
+ def general_to_imap_search_key(key, value)
120
+ [key, value]
121
+ end
122
+
123
+ def boolean_to_imap_search_key(key, value)
124
+ if value
125
+ [key]
126
+ else
127
+ ['NOT', key]
128
+ end
129
+ end
130
+
131
+ def message_ids
132
+ imap_client.imap.search(to_imap_search_keys)
133
+ end
134
+ alias_method :all, :message_ids
135
+
136
+ private
137
+
138
+ def initialize(imap_client = nil, criteria = nil)
139
+ @criteria = criteria || {}
140
+ @imap_client = imap_client
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,3 @@
1
+ class Imap
2
+ VERSION = '0.4.0'
3
+ end
data/lib/imap.rb ADDED
@@ -0,0 +1,82 @@
1
+ # imap.rb
2
+ # Imap
3
+
4
+ require 'net/imap'
5
+ require_relative './Imap/Message'
6
+
7
+ class Imap
8
+ class << self
9
+
10
+ def setup(config)
11
+ raise ArgumentError, "Server must be specified" unless config[:server]
12
+ imap_client = Imap.new(**config)
13
+ if config[:username] && config[:password]
14
+ imap_client.login(username: config[:username], password: config[:password])
15
+ end
16
+ if config[:mailbox]
17
+ imap_client.mailbox = config[:mailbox]
18
+ end
19
+ imap_client
20
+ end
21
+
22
+ end # class << self
23
+
24
+ attr_accessor :server
25
+ attr_accessor :ssl
26
+ attr_accessor :username
27
+ attr_accessor :password
28
+ attr_reader :mailbox
29
+
30
+ def login(username: nil, password: nil)
31
+ username ||= @username
32
+ password ||= @password
33
+ begin
34
+ imap.login(username, password)
35
+ true
36
+ rescue Net::IMAP::NoResponseError, Net::IMAP::BadResponseError
37
+ false
38
+ end
39
+ end
40
+
41
+ def mailbox=(mailbox)
42
+ @mailbox = mailbox
43
+ imap.select(mailbox)
44
+ end
45
+
46
+ def search(criteria = {})
47
+ Imap::Message.search(self, **criteria)
48
+ end
49
+ alias_method :messages, :search
50
+ alias_method :find, :search
51
+
52
+ def bye
53
+ logout
54
+ disconnect
55
+ end
56
+
57
+ def logout
58
+ imap.logout
59
+ end
60
+
61
+ def disconnect
62
+ imap.disconnect
63
+ end
64
+
65
+ def imap
66
+ @imap ||= Net::IMAP.new(server, ssl: @ssl)
67
+ end
68
+
69
+ def ssl?
70
+ @ssl
71
+ end
72
+
73
+ private
74
+
75
+ def initialize(server:, ssl: true, username: nil, password: nil, mailbox: 'INBOX')
76
+ @server = server
77
+ @ssl = ssl
78
+ @username = username
79
+ @password = password
80
+ @mailbox = mailbox
81
+ end
82
+ end
@@ -0,0 +1,112 @@
1
+ # test/Imap/Message_test.rb
2
+
3
+ require_relative '../test_helper'
4
+
5
+ describe Imap::Message do
6
+ let(:client) do
7
+ mock_imap = MockIMAP.new('imap.example.com', ssl: true)
8
+ Net::IMAP.stub(:new, mock_imap) do
9
+ Imap.setup(
10
+ server: 'imap.example.com',
11
+ username: 'user@example.com',
12
+ password: 'secret'
13
+ )
14
+ end
15
+ end
16
+
17
+ let(:imap_message) do
18
+ Imap::Message.new(1, client)
19
+ end
20
+
21
+ describe '.search' do
22
+ it 'returns an array of messages' do
23
+ messages = Imap::Message.search(client, from: 'test@example.com')
24
+ _(messages).must_be_instance_of Array
25
+ _(messages.first).must_be_instance_of Imap::Message
26
+ end
27
+
28
+ it 'has find alias' do
29
+ _((Imap::Message.method(:find) == Imap::Message.method(:search))).must_equal true
30
+ end
31
+ end
32
+
33
+ describe '#body' do
34
+ it 'returns the message body' do
35
+ _(imap_message.body).must_match /Mock body/
36
+ end
37
+
38
+ it 'caches the result' do
39
+ first_call = imap_message.body
40
+ second_call = imap_message.body
41
+ _(first_call.object_id).must_equal second_call.object_id
42
+ end
43
+ end
44
+
45
+ describe '#subject' do
46
+ it 'returns the subject without prefix' do
47
+ _(imap_message.subject).must_match(/Mock Subject/)
48
+ _(imap_message.subject).wont_match(/^Subject:/)
49
+ end
50
+
51
+ it 'strips whitespace' do
52
+ _(imap_message.subject).must_equal(imap_message.subject.strip)
53
+ end
54
+ end
55
+
56
+ describe '#from' do
57
+ it 'returns the from address' do
58
+ _(imap_message.from).must_match(/sender@example.com/)
59
+ end
60
+
61
+ it 'removes From: prefix' do
62
+ _(imap_message.from).wont_match(/^From:/)
63
+ end
64
+ end
65
+
66
+ describe '#to' do
67
+ it 'returns an array of email addresses' do
68
+ addresses = imap_message.to
69
+ _(addresses).must_be_instance_of(Array)
70
+ _(addresses.first).must_match(/@/)
71
+ end
72
+
73
+ it 'formats addresses correctly' do
74
+ addresses = imap_message.to
75
+ _(addresses.first).must_equal('user@example.com')
76
+ end
77
+ end
78
+
79
+ describe '#urls' do
80
+ it 'extracts http URLs from body' do
81
+ imap_message.stub(:body, 'Check out http://example.com and https://test.com') do
82
+ urls = imap_message.urls
83
+ _(urls).must_include('http://example.com')
84
+ _(urls).must_include('https://test.com')
85
+ end
86
+ end
87
+
88
+ it 'returns empty array when no URLs' do
89
+ imap_message.stub(:body, 'No URLs here') do
90
+ _(urls = imap_message.urls).must_be_empty
91
+ end
92
+ end
93
+ end
94
+
95
+ describe '#mark_as_seen' do
96
+ it 'marks the message as seen' do
97
+ _(imap_message.mark_as_seen).must_be_nil
98
+ end
99
+
100
+ it 'has mark_as_read alias' do
101
+ _((imap_message.method(:mark_as_read) == imap_message.method(:mark_as_seen))).must_equal true
102
+ end
103
+ end
104
+
105
+ describe '#initialize' do
106
+ it 'accepts message_id and imap_client' do
107
+ test_message = Imap::Message.new(42, client)
108
+ _(test_message.message_id).must_equal(42)
109
+ _(test_message.imap_client).must_equal(client)
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,92 @@
1
+ # test/Imap/Search_test.rb
2
+
3
+ require_relative '../test_helper'
4
+
5
+ describe Imap::Search do
6
+ let(:client) do
7
+ mock_imap = MockIMAP.new('imap.example.com', ssl: true)
8
+ Net::IMAP.stub(:new, mock_imap) do
9
+ Imap.setup(
10
+ server: 'imap.example.com',
11
+ username: 'user@example.com',
12
+ password: 'secret'
13
+ )
14
+ end
15
+ end
16
+
17
+ let(:search) do
18
+ Imap::Search.new(client, {})
19
+ end
20
+
21
+ describe '#to_imap_search_keys' do
22
+ it 'converts general search keys' do
23
+ test_search = Imap::Search.new(client, from: 'test@example.com')
24
+ keys = test_search.to_imap_search_keys
25
+ _(keys).must_include 'FROM'
26
+ _(keys).must_include 'test@example.com'
27
+ end
28
+
29
+ it 'converts boolean search keys' do
30
+ test_search = Imap::Search.new(client, seen: false)
31
+ keys = test_search.to_imap_search_keys
32
+ _(keys).must_include 'NOT'
33
+ _(keys).must_include 'SEEN'
34
+ end
35
+
36
+ it 'handles multiple criteria' do
37
+ test_search = Imap::Search.new(client, from: 'test@example.com', seen: true)
38
+ keys = test_search.to_imap_search_keys
39
+ _(keys.length).must_be :>, 2
40
+ end
41
+ end
42
+
43
+ describe '#message_ids' do
44
+ it 'returns an array of message IDs' do
45
+ ids = search.message_ids
46
+ _(ids).must_be_instance_of Array
47
+ end
48
+
49
+ it 'has all alias' do
50
+ _((search.method(:all) == search.method(:message_ids))).must_equal true
51
+ end
52
+ end
53
+
54
+ describe '#boolean_operator?' do
55
+ it 'returns true for boolean operators' do
56
+ _(search.boolean_operator?('SEEN')).must_equal true
57
+ _(search.boolean_operator?('ANSWERED')).must_equal true
58
+ end
59
+
60
+ it 'returns false for general operators' do
61
+ _(search.boolean_operator?('FROM')).must_equal false
62
+ end
63
+ end
64
+
65
+ describe '#general_operator?' do
66
+ it 'returns true for general operators' do
67
+ _(search.general_operator?('FROM')).must_equal true
68
+ _(search.general_operator?('SUBJECT')).must_equal true
69
+ end
70
+
71
+ it 'returns false for boolean operators' do
72
+ _(search.general_operator?('SEEN')).must_equal false
73
+ end
74
+ end
75
+
76
+ describe '#initialize' do
77
+ it 'accepts imap_client parameter' do
78
+ test_search = Imap::Search.new(client)
79
+ _(test_search.imap_client).must_equal client
80
+ end
81
+
82
+ it 'accepts criteria parameter' do
83
+ test_search = Imap::Search.new(client, from: 'test@example.com')
84
+ _(test_search.criteria).must_equal({ from: 'test@example.com' })
85
+ end
86
+
87
+ it 'defaults criteria to empty hash' do
88
+ test_search = Imap::Search.new(client)
89
+ _(test_search.criteria).must_equal({})
90
+ end
91
+ end
92
+ end
data/test/imap_test.rb ADDED
@@ -0,0 +1,119 @@
1
+ # test/imap_test.rb
2
+
3
+ require_relative './test_helper'
4
+
5
+ describe Imap do
6
+ let(:client) do
7
+ Net::IMAP.stub(:new, MockIMAP.new('imap.example.com', ssl: true)) do
8
+ Imap.setup(
9
+ server: 'imap.example.com',
10
+ username: 'user@example.com',
11
+ password: 'secret',
12
+ mailbox: 'INBOX'
13
+ )
14
+ end
15
+ end
16
+
17
+ describe ".setup" do
18
+ it 'creates a new client instance' do
19
+ _(client).must_be_instance_of(Imap)
20
+ end
21
+
22
+ it 'requires a server parameter' do
23
+ _(proc{Imap.setup({})}).must_raise(ArgumentError)
24
+ end
25
+
26
+ it 'sets the server' do
27
+ _(client.server).must_equal 'imap.example.com'
28
+ end
29
+
30
+ it 'defaults ssl to true' do
31
+ _(client.ssl?).must_equal true
32
+ end
33
+
34
+ it 'defaults mailbox to INBOX' do
35
+ _(client.mailbox).must_equal 'INBOX'
36
+ end
37
+ end
38
+
39
+ describe "#login" do
40
+ it 'returns true on successful login' do
41
+ Net::IMAP.stub(:new, MockIMAP.new('imap.example.com', ssl: true)) do
42
+ test_client = Imap.new(server: 'imap.example.com')
43
+ result = test_client.login(username: 'user', password: 'pass')
44
+ _(result).must_equal true
45
+ end
46
+ end
47
+
48
+ it 'returns false on failed login' do
49
+ mock = MockIMAP.new('imap.example.com', ssl: true)
50
+ def mock.login(username, password)
51
+ mock_response = OpenStruct.new(data: OpenStruct.new(text: 'Authentication failed'))
52
+ raise Net::IMAP::BadResponseError, mock_response
53
+ end
54
+
55
+ Net::IMAP.stub(:new, mock) do
56
+ test_client = Imap.new(server: 'imap.example.com')
57
+ result = test_client.login(username: 'user', password: 'wrong')
58
+ _(result).must_equal false
59
+ end
60
+ end
61
+ end
62
+
63
+ describe "#mailbox=" do
64
+ it 'sets the mailbox' do
65
+ Net::IMAP.stub(:new, MockIMAP.new('imap.example.com', ssl: true)) do
66
+ test_client = Imap.new(server: 'imap.example.com')
67
+ test_client.mailbox = 'Sent'
68
+ _(test_client.mailbox).must_equal 'Sent'
69
+ end
70
+ end
71
+ end
72
+
73
+ describe "#search" do
74
+ it 'returns an array of Message objects' do
75
+ Net::IMAP.stub(:new, MockIMAP.new('imap.example.com', ssl: true)) do
76
+ test_client = Imap.setup(
77
+ server: 'imap.example.com',
78
+ username: 'user',
79
+ password: 'pass'
80
+ )
81
+ messages = test_client.search(from: 'test@example.com')
82
+ _(messages).must_be_instance_of Array
83
+ _(messages.first).must_be_instance_of Imap::Message
84
+ end
85
+ end
86
+
87
+ it 'has messages and find aliases' do
88
+ _((client.method(:messages) == client.method(:search))).must_equal true
89
+ _((client.method(:find) == client.method(:search))).must_equal true
90
+ end
91
+ end
92
+
93
+ describe "#initialize" do
94
+ it 'accepts server parameter' do
95
+ test_client = Imap.new(server: 'mail.example.com')
96
+ _(test_client.server).must_equal 'mail.example.com'
97
+ end
98
+
99
+ it 'accepts ssl parameter' do
100
+ test_client = Imap.new(server: 'mail.example.com', ssl: false)
101
+ _(test_client.ssl?).must_equal false
102
+ end
103
+
104
+ it 'accepts username parameter' do
105
+ test_client = Imap.new(server: 'mail.example.com', username: 'test@example.com')
106
+ _(test_client.username).must_equal 'test@example.com'
107
+ end
108
+
109
+ it 'accepts password parameter' do
110
+ test_client = Imap.new(server: 'mail.example.com', password: 'secret')
111
+ _(test_client.password).must_equal 'secret'
112
+ end
113
+
114
+ it 'accepts mailbox parameter' do
115
+ test_client = Imap.new(server: 'mail.example.com', mailbox: 'Sent')
116
+ _(test_client.mailbox).must_equal 'Sent'
117
+ end
118
+ end
119
+ end
data/test/test_all.rb ADDED
@@ -0,0 +1,6 @@
1
+ # test/test_all.rb
2
+
3
+ require_relative './test_helper'
4
+ require_relative './Imap_test'
5
+ require_relative './Imap/Message_test'
6
+ require_relative './Imap/Search_test'
@@ -0,0 +1,74 @@
1
+ # test/test_helper.rb
2
+
3
+ require 'minitest/autorun'
4
+ require 'minitest/spec'
5
+ require 'ostruct'
6
+ require_relative '../lib/imap'
7
+
8
+ class MockIMAP
9
+ attr_reader :login_called
10
+ attr_reader :logout_called
11
+ attr_reader :search_called
12
+ attr_reader :search_criteria
13
+ attr_reader :select_called
14
+ attr_reader :selected_mailbox
15
+
16
+ def login(username, password)
17
+ @login_called = true
18
+ @username = username
19
+ @password = password
20
+ end
21
+
22
+ def select(mailbox)
23
+ @select_called = true
24
+ @selected_mailbox = mailbox
25
+ end
26
+
27
+ def search(criteria)
28
+ @search_called = true
29
+ @search_criteria = criteria
30
+ [1, 2, 3]
31
+ end
32
+
33
+ def fetch(message_id, attrs)
34
+ [OpenStruct.new(attr: mock_fetch_attrs(message_id, attrs))]
35
+ end
36
+
37
+ def store(message_id, flags, values); end
38
+
39
+ def logout
40
+ @logout_called = true
41
+ end
42
+
43
+ def disconnect; end
44
+
45
+ private
46
+
47
+ def initialize(server, ssl:)
48
+ @server = server
49
+ @ssl = ssl
50
+ @login_called = false
51
+ @select_called = false
52
+ @search_called = false
53
+ @logout_called = false
54
+ end
55
+
56
+ def mock_fetch_attrs(message_id, attrs)
57
+ result = {}
58
+ attrs.each do |attr|
59
+ case attr
60
+ when 'BODY[TEXT]'
61
+ result['BODY[TEXT]'] = "Mock body for message #{message_id}"
62
+ when 'BODY[HEADER.FIELDS (SUBJECT)]'
63
+ result['BODY[HEADER.FIELDS (SUBJECT)]'] = "Subject: Mock Subject #{message_id}\r\n"
64
+ when 'BODY[HEADER.FIELDS (FROM)]'
65
+ result['BODY[HEADER.FIELDS (FROM)]'] = "From: sender@example.com\r\n"
66
+ when 'ENVELOPE'
67
+ result['ENVELOPE'] = OpenStruct.new(
68
+ to: [OpenStruct.new(mailbox: 'user', host: 'example.com')]
69
+ )
70
+ end
71
+ end
72
+ result
73
+ end
74
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: imap
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.0
5
+ platform: ruby
6
+ authors:
7
+ - thoran
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2025-12-20 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: net-imap
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '0.4'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '0.4'
26
+ - !ruby/object:Gem::Dependency
27
+ name: minitest
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '5.0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '5.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rake
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '13.0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '13.0'
54
+ description: A Ruby wrapper around Net::IMAP with an elegant DSL for searching, reading,
55
+ and managing email.
56
+ email: code@thoran.com
57
+ executables: []
58
+ extensions: []
59
+ extra_rdoc_files: []
60
+ files:
61
+ - README.md
62
+ - imap.gemspec
63
+ - lib/Imap/Message.rb
64
+ - lib/Imap/Search.rb
65
+ - lib/Imap/VERSION.rb
66
+ - lib/imap.rb
67
+ - test/Imap/Message_test.rb
68
+ - test/Imap/Search_test.rb
69
+ - test/imap_test.rb
70
+ - test/test_all.rb
71
+ - test/test_helper.rb
72
+ homepage: https://github.com/thoran/imap
73
+ licenses:
74
+ - MIT
75
+ metadata: {}
76
+ rdoc_options: []
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '2.7'
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ requirements: []
90
+ rubygems_version: 4.0.2
91
+ specification_version: 4
92
+ summary: IMAP for Ruby made easy.
93
+ test_files: []