Skip to content

Commit 7b5ba32

Browse files
committed
imapmemserver: implement minimal NOTIFY support
This is just enough support to add round-trip tests for the basic portion.
1 parent 0d34cdf commit 7b5ba32

File tree

4 files changed

+406
-0
lines changed

4 files changed

+406
-0
lines changed

imapclient/client_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ func newMemClientServerPair(t *testing.T) (net.Conn, io.Closer) {
102102
Caps: imap.CapSet{
103103
imap.CapIMAP4rev1: {},
104104
imap.CapIMAP4rev2: {},
105+
imap.CapNotify: {},
105106
},
106107
})
107108

imapclient/notify_test.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package imapclient_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/emersion/go-imap/v2"
7+
)
8+
9+
func TestClient_Notify(t *testing.T) {
10+
client, server := newClientServerPair(t, imap.ConnStateAuthenticated)
11+
defer client.Close()
12+
defer server.Close()
13+
14+
// Test NOTIFY with SELECTED mailbox
15+
options := &imap.NotifyOptions{
16+
Items: []imap.NotifyItem{
17+
{
18+
MailboxSpec: imap.NotifyMailboxSpecSelected,
19+
Events: []imap.NotifyEvent{
20+
imap.NotifyEventMessageNew,
21+
imap.NotifyEventMessageExpunge,
22+
},
23+
},
24+
},
25+
}
26+
27+
// Note: The test server's stub implementation refuses NOTIFY with
28+
// NO [NOTIFICATIONOVERFLOW], which is RFC-compliant per RFC 5465 Section 3.1.
29+
_, err := client.Notify(options)
30+
if err == nil {
31+
t.Fatal("Expected error from stub implementation")
32+
}
33+
34+
imapErr, ok := err.(*imap.Error)
35+
if !ok {
36+
t.Fatalf("Expected *imap.Error, got %T", err)
37+
}
38+
if imapErr.Type != imap.StatusResponseTypeNo {
39+
t.Errorf("Expected NO response, got %v", imapErr.Type)
40+
}
41+
if imapErr.Code != imap.ResponseCodeNotificationOverflow {
42+
t.Errorf("Expected NOTIFICATIONOVERFLOW code, got %v", imapErr.Code)
43+
}
44+
}
45+
46+
func TestClient_NotifyNone(t *testing.T) {
47+
client, server := newClientServerPair(t, imap.ConnStateAuthenticated)
48+
defer client.Close()
49+
defer server.Close()
50+
51+
// Note: The test server's stub implementation refuses NOTIFY with
52+
// NO [NOTIFICATIONOVERFLOW]
53+
err := client.NotifyNone()
54+
if err == nil {
55+
t.Fatal("Expected error from stub implementation")
56+
}
57+
imapErr, ok := err.(*imap.Error)
58+
if !ok {
59+
t.Fatalf("Expected *imap.Error, got %T", err)
60+
}
61+
if imapErr.Type != imap.StatusResponseTypeNo {
62+
t.Errorf("Expected NO response, got %v", imapErr.Type)
63+
}
64+
if imapErr.Code != imap.ResponseCodeNotificationOverflow {
65+
t.Errorf("Expected NOTIFICATIONOVERFLOW code, got %v", imapErr.Code)
66+
}
67+
}
68+
69+
func TestClient_NotifyMultiple(t *testing.T) {
70+
client, server := newClientServerPair(t, imap.ConnStateAuthenticated)
71+
defer client.Close()
72+
defer server.Close()
73+
74+
// Test NOTIFY with multiple items
75+
options := &imap.NotifyOptions{
76+
STATUS: true,
77+
Items: []imap.NotifyItem{
78+
{
79+
MailboxSpec: imap.NotifyMailboxSpecSelected,
80+
Events: []imap.NotifyEvent{
81+
imap.NotifyEventMessageNew,
82+
imap.NotifyEventMessageExpunge,
83+
},
84+
},
85+
{
86+
MailboxSpec: imap.NotifyMailboxSpecPersonal,
87+
Events: []imap.NotifyEvent{
88+
imap.NotifyEventMailboxName,
89+
imap.NotifyEventSubscriptionChange,
90+
},
91+
},
92+
},
93+
}
94+
95+
// Note: The test server's stub implementation refuses NOTIFY with
96+
// NO [NOTIFICATIONOVERFLOW]
97+
_, err := client.Notify(options)
98+
if err == nil {
99+
t.Fatal("Expected error from stub implementation")
100+
}
101+
102+
imapErr, ok := err.(*imap.Error)
103+
if !ok {
104+
t.Fatalf("Expected *imap.Error, got %T", err)
105+
}
106+
if imapErr.Type != imap.StatusResponseTypeNo {
107+
t.Errorf("Expected NO response, got %v", imapErr.Type)
108+
}
109+
if imapErr.Code != imap.ResponseCodeNotificationOverflow {
110+
t.Errorf("Expected NOTIFICATIONOVERFLOW code, got %v", imapErr.Code)
111+
}
112+
}

imapserver/imapmemserver/session.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,3 +138,20 @@ func (sess *UserSession) Idle(w *imapserver.UpdateWriter, stop <-chan struct{})
138138
}
139139
return sess.mailbox.Idle(w, stop)
140140
}
141+
142+
func (sess *UserSession) Notify(w *imapserver.UpdateWriter, options *imap.NotifyOptions) error {
143+
// Refuse the NOTIFY request with NO [NOTIFICATIONOVERFLOW] to indicate
144+
// the server is unable/unwilling to deliver notifications.
145+
//
146+
// Per RFC 5465 Section 3.1 (lines 327-330):
147+
// "If the notification would be prohibitively expensive for the server
148+
// (e.g., "notify me of all flag changes in all mailboxes"), the server
149+
// MAY refuse the command with a tagged NO [NOTIFICATIONOVERFLOW] response."
150+
//
151+
// This is a simple RFC-compliant stub. Implementing full NOTIFY support is pending.
152+
return &imap.Error{
153+
Type: imap.StatusResponseTypeNo,
154+
Code: imap.ResponseCodeNotificationOverflow,
155+
Text: "Request not implemented",
156+
}
157+
}

0 commit comments

Comments
 (0)