Skip to content

Commit 6c91a67

Browse files
authored
Merge pull request #7 from systemli/Implement-Senders-Handler
✨ Implement Senders Handler
2 parents 0b1fc45 + 4520374 commit 6c91a67

File tree

6 files changed

+221
-1
lines changed

6 files changed

+221
-1
lines changed

adapter.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,35 @@ func (p *PostfixAdapter) MailboxHandler(conn net.Conn) {
104104
_, _ = conn.Write([]byte("200 1\n"))
105105
}
106106

107+
// SendersHandler handles the get command for senders.
108+
// It fetches the senders for the given email.
109+
// The response is a comma separated list of senders.
110+
func (p *PostfixAdapter) SendersHandler(conn net.Conn) {
111+
defer conn.Close()
112+
113+
payload, err := p.payload(conn)
114+
if err != nil {
115+
fmt.Println("Error getting payload:", err.Error())
116+
_, _ = conn.Write([]byte("400 Error getting payload\n"))
117+
return
118+
}
119+
120+
email := strings.TrimSuffix(payload, "\n")
121+
senders, err := p.client.GetSenders(string(email))
122+
if err != nil {
123+
fmt.Println("Error fetching senders:", err.Error())
124+
_, _ = conn.Write([]byte("400 Error fetching senders\n"))
125+
return
126+
}
127+
128+
if len(senders) == 0 {
129+
_, _ = conn.Write([]byte("500 NO%20RESULT\n"))
130+
return
131+
}
132+
133+
_, _ = conn.Write([]byte(fmt.Sprintf("200 %s \n", strings.Join(senders, ","))))
134+
}
135+
107136
// payload reads the data from the connection. It checks for valid
108137
// commands sent by postfix and returns the payload.
109138
func (h *PostfixAdapter) payload(conn net.Conn) (string, error) {

adapter_test.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,95 @@ func (s *AdapterTestSuite) TestMailboxHandler() {
244244
})
245245
}
246246

247+
func (s *AdapterTestSuite) TestSendersHandler() {
248+
userli := new(MockUserliService)
249+
userli.On("GetSenders", "user@example.com").Return([]string{"user@example.com"}, nil)
250+
userli.On("GetSenders", "alias@example.com").Return([]string{"user1@example.com", "user2@example.com"}, nil)
251+
userli.On("GetSenders", "error@example.com").Return([]string{}, errors.New("error"))
252+
userli.On("GetSenders", "nonexisting@example.com").Return([]string{}, nil)
253+
254+
portNumber, _ := rand.Int(rand.Reader, big.NewInt(65535-20000))
255+
portNumber.Add(portNumber, big.NewInt(20000))
256+
listen := ":" + portNumber.String()
257+
258+
adapter := NewPostfixAdapter(userli)
259+
260+
go StartTCPServer(s.ctx, s.wg, listen, adapter.SendersHandler)
261+
262+
// wait until the server is ready
263+
for {
264+
conn, err := net.Dial("tcp", listen)
265+
if err == nil {
266+
conn.Close()
267+
break
268+
}
269+
}
270+
271+
s.Run("success", func() {
272+
conn, err := net.Dial("tcp", listen)
273+
s.NoError(err)
274+
275+
_, err = conn.Write([]byte("get user@example.com"))
276+
s.NoError(err)
277+
278+
response := make([]byte, 4096)
279+
_, err = conn.Read(response)
280+
s.NoError(err)
281+
282+
s.Equal("200 user@example.com \n", string(bytes.Trim(response, "\x00")))
283+
284+
conn.Close()
285+
})
286+
287+
s.Run("alias success", func() {
288+
conn, err := net.Dial("tcp", listen)
289+
s.NoError(err)
290+
291+
_, err = conn.Write([]byte("get alias@example.com"))
292+
s.NoError(err)
293+
294+
response := make([]byte, 4096)
295+
_, err = conn.Read(response)
296+
s.NoError(err)
297+
298+
s.Equal("200 user1@example.com,user2@example.com \n", string(bytes.Trim(response, "\x00")))
299+
300+
conn.Close()
301+
})
302+
303+
s.Run("error", func() {
304+
conn, err := net.Dial("tcp", listen)
305+
s.NoError(err)
306+
307+
_, err = conn.Write([]byte("get error@example.com"))
308+
s.NoError(err)
309+
310+
response := make([]byte, 4096)
311+
_, err = conn.Read(response)
312+
s.NoError(err)
313+
314+
s.Equal("400 Error fetching senders\n", string(bytes.Trim(response, "\x00")))
315+
316+
conn.Close()
317+
})
318+
319+
s.Run("empty result", func() {
320+
conn, err := net.Dial("tcp", listen)
321+
s.NoError(err)
322+
323+
_, err = conn.Write([]byte("get nonexisting@example.com"))
324+
s.NoError(err)
325+
326+
response := make([]byte, 4096)
327+
_, err = conn.Read(response)
328+
s.NoError(err)
329+
330+
s.Equal("500 NO%20RESULT\n", string(bytes.Trim(response, "\x00")))
331+
332+
conn.Close()
333+
})
334+
}
335+
247336
func TestAdapterTestSuite(t *testing.T) {
248337
suite.Run(t, new(AdapterTestSuite))
249338
}

main.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ func main() {
4242
mailboxListenAddr = ":10003"
4343
}
4444

45+
sendersListenAddr := os.Getenv("SENDERS_LISTEN_ADDR")
46+
if sendersListenAddr == "" {
47+
sendersListenAddr = ":10004"
48+
}
49+
4550
userli := NewUserli(userliToken, userliBaseURL)
4651
adapter := NewPostfixAdapter(userli)
4752

@@ -50,10 +55,11 @@ func main() {
5055

5156
var wg sync.WaitGroup
5257

53-
wg.Add(3)
58+
wg.Add(4)
5459
go StartTCPServer(ctx, &wg, aliasListenAddr, adapter.AliasHandler)
5560
go StartTCPServer(ctx, &wg, domainListenAddr, adapter.DomainHandler)
5661
go StartTCPServer(ctx, &wg, mailboxListenAddr, adapter.MailboxHandler)
62+
go StartTCPServer(ctx, &wg, sendersListenAddr, adapter.SendersHandler)
5763

5864
wg.Wait()
5965
fmt.Println("Servers stopped")

mock_UserliService.go

Lines changed: 30 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

userli.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ type UserliService interface {
1111
GetAliases(email string) ([]string, error)
1212
GetDomain(domain string) (bool, error)
1313
GetMailbox(email string) (bool, error)
14+
GetSenders(email string) ([]string, error)
1415
}
1516

1617
type Userli struct {
@@ -73,6 +74,21 @@ func (u *Userli) GetMailbox(email string) (bool, error) {
7374
return result, nil
7475
}
7576

77+
func (u *Userli) GetSenders(email string) ([]string, error) {
78+
resp, err := u.call(fmt.Sprintf("%s/api/postfix/senders/%s", u.baseURL, email))
79+
if err != nil {
80+
return []string{}, err
81+
}
82+
83+
var senders []string
84+
err = json.NewDecoder(resp.Body).Decode(&senders)
85+
if err != nil {
86+
return []string{}, err
87+
}
88+
89+
return senders, nil
90+
}
91+
7692
func (u *Userli) call(url string) (*http.Response, error) {
7793
req, err := http.NewRequest("GET", url, nil)
7894
if err != nil {

userli_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,56 @@ func (s *UserliTestSuite) TestGetMailbox() {
153153
})
154154
}
155155

156+
func (s *UserliTestSuite) TestGetSenders() {
157+
s.Run("success", func() {
158+
gock.New("http://localhost:8000").
159+
Get("/api/postfix/senders/user@example.com").
160+
MatchHeader("Authentication", "Bearer insecure").
161+
MatchHeader("Accept", "application/json").
162+
MatchHeader("Content-Type", "application/json").
163+
MatchHeader("User-Agent", "userli-postfix-adapter").
164+
Reply(200).
165+
JSON([]string{"user@example.com"})
166+
167+
senders, err := s.userli.GetSenders("user@example.com")
168+
s.NoError(err)
169+
s.Equal([]string{"user@example.com"}, senders)
170+
s.True(gock.IsDone())
171+
})
172+
173+
s.Run("alias success", func() {
174+
gock.New("http://localhost:8000").
175+
Get("/api/postfix/senders/alias@example.com").
176+
MatchHeader("Authentication", "Bearer insecure").
177+
MatchHeader("Accept", "application/json").
178+
MatchHeader("Content-Type", "application/json").
179+
MatchHeader("User-Agent", "userli-postfix-adapter").
180+
Reply(200).
181+
JSON([]string{"user1@example.com", "user2@example.com"})
182+
183+
senders, err := s.userli.GetSenders("alias@example.com")
184+
s.NoError(err)
185+
s.Equal([]string{"user1@example.com", "user2@example.com"}, senders)
186+
s.True(gock.IsDone())
187+
})
188+
189+
s.Run("error", func() {
190+
gock.New("http://localhost:8000").
191+
Get("/api/postfix/senders/user@example.com").
192+
MatchHeader("Authentication", "Bearer insecure").
193+
MatchHeader("Accept", "application/json").
194+
MatchHeader("Content-Type", "application/json").
195+
MatchHeader("User-Agent", "userli-postfix-adapter").
196+
Reply(500).
197+
JSON(map[string]string{"error": "internal server error"})
198+
199+
senders, err := s.userli.GetSenders("user@example.com")
200+
s.Error(err)
201+
s.Empty(senders)
202+
s.True(gock.IsDone())
203+
})
204+
}
205+
156206
func TestUserl(t *testing.T) {
157207
suite.Run(t, new(UserliTestSuite))
158208
}

0 commit comments

Comments
 (0)