Skip to content

Commit

Permalink
Merge pull request #6 from systemli/Implement-Mailbox-Handler
Browse files Browse the repository at this point in the history
✨ Implement Mailbox Handler
  • Loading branch information
0x46616c6b authored Oct 2, 2024
2 parents bb34ff7 + 99cea0f commit 0b1fc45
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 4 deletions.
29 changes: 29 additions & 0 deletions adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,35 @@ func (p *PostfixAdapter) DomainHandler(conn net.Conn) {
_, _ = conn.Write([]byte("200 1\n"))
}

// MailboxHandler handles the get command for mailboxes.
// It checks if the mailbox exists.
// The response is a single line with the status code.
func (p *PostfixAdapter) MailboxHandler(conn net.Conn) {
defer conn.Close()

payload, err := p.payload(conn)
if err != nil {
fmt.Println("Error getting payload:", err.Error())
_, _ = conn.Write([]byte("400 Error getting payload\n"))
return
}

email := strings.TrimSuffix(payload, "\n")
exists, err := p.client.GetMailbox(string(email))
if err != nil {
fmt.Println("Error fetching mailbox:", err.Error())
_, _ = conn.Write([]byte("400 Error fetching mailbox\n"))
return
}

if !exists {
_, _ = conn.Write([]byte("500 NO%20RESULT\n"))
return
}

_, _ = conn.Write([]byte("200 1\n"))
}

// payload reads the data from the connection. It checks for valid
// commands sent by postfix and returns the payload.
func (h *PostfixAdapter) payload(conn net.Conn) (string, error) {
Expand Down
79 changes: 76 additions & 3 deletions adapter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@ import (
type AdapterTestSuite struct {
suite.Suite

ctx context.Context
wg *sync.WaitGroup
client UserliService
ctx context.Context
wg *sync.WaitGroup
}

func (s *AdapterTestSuite) SetupTest() {
Expand Down Expand Up @@ -96,6 +95,8 @@ func (s *AdapterTestSuite) TestAliasHandler() {
_, err = conn.Read(response)
s.NoError(err)
s.Equal("400 Error fetching aliases\n", string(bytes.Trim(response, "\x00")))

conn.Close()
})
}

Expand Down Expand Up @@ -171,6 +172,78 @@ func (s *AdapterTestSuite) TestDomainHandler() {
})
}

func (s *AdapterTestSuite) TestMailboxHandler() {
userli := new(MockUserliService)
userli.On("GetMailbox", "[email protected]").Return(true, nil)
userli.On("GetMailbox", "[email protected]").Return(false, nil)
userli.On("GetMailbox", "[email protected]").Return(false, errors.New("error"))

portNumber, _ := rand.Int(rand.Reader, big.NewInt(65535-20000))
portNumber.Add(portNumber, big.NewInt(20000))
listen := ":" + portNumber.String()

adapter := NewPostfixAdapter(userli)

go StartTCPServer(s.ctx, s.wg, listen, adapter.MailboxHandler)

// wait until the server is ready
for {
conn, err := net.Dial("tcp", listen)
if err == nil {
conn.Close()
break
}
}

s.Run("success", func() {
conn, err := net.Dial("tcp", listen)
s.NoError(err)

_, err = conn.Write([]byte("get [email protected]"))
s.NoError(err)

response := make([]byte, 4096)
_, err = conn.Read(response)
s.NoError(err)

s.Equal("200 1\n", string(bytes.Trim(response, "\x00")))

conn.Close()
})

s.Run("not found", func() {
conn, err := net.Dial("tcp", listen)
s.NoError(err)

_, err = conn.Write([]byte("get [email protected]"))
s.NoError(err)

response := make([]byte, 4096)
_, err = conn.Read(response)
s.NoError(err)

s.Equal("500 NO%20RESULT\n", string(bytes.Trim(response, "\x00")))

conn.Close()
})

s.Run("error", func() {
conn, err := net.Dial("tcp", listen)
s.NoError(err)

_, err = conn.Write([]byte("get [email protected]"))
s.NoError(err)

response := make([]byte, 4096)
_, err = conn.Read(response)
s.NoError(err)

s.Equal("400 Error fetching mailbox\n", string(bytes.Trim(response, "\x00")))

conn.Close()
})
}

func TestAdapterTestSuite(t *testing.T) {
suite.Run(t, new(AdapterTestSuite))
}
8 changes: 7 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ func main() {
domainListenAddr = ":10002"
}

mailboxListenAddr := os.Getenv("MAILBOX_LISTEN_ADDR")
if mailboxListenAddr == "" {
mailboxListenAddr = ":10003"
}

userli := NewUserli(userliToken, userliBaseURL)
adapter := NewPostfixAdapter(userli)

Expand All @@ -45,9 +50,10 @@ func main() {

var wg sync.WaitGroup

wg.Add(2)
wg.Add(3)
go StartTCPServer(ctx, &wg, aliasListenAddr, adapter.AliasHandler)
go StartTCPServer(ctx, &wg, domainListenAddr, adapter.DomainHandler)
go StartTCPServer(ctx, &wg, mailboxListenAddr, adapter.MailboxHandler)

wg.Wait()
fmt.Println("Servers stopped")
Expand Down
28 changes: 28 additions & 0 deletions mock_UserliService.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions sonar-project.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ sonar.links.homepage=https://github.com/systemli/userli-postfix-adapter
sonar.sourceEncoding=UTF-8

sonar.sources=.
sonar.exclusions=**/mock_*.go

sonar.tests=.
sonar.test.inclusions=**/*_test.go
Expand Down
16 changes: 16 additions & 0 deletions userli.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
type UserliService interface {
GetAliases(email string) ([]string, error)
GetDomain(domain string) (bool, error)
GetMailbox(email string) (bool, error)
}

type Userli struct {
Expand Down Expand Up @@ -57,6 +58,21 @@ func (u *Userli) GetDomain(domain string) (bool, error) {
return result, nil
}

func (u *Userli) GetMailbox(email string) (bool, error) {
resp, err := u.call(fmt.Sprintf("%s/api/postfix/mailbox/%s", u.baseURL, email))
if err != nil {
return false, err
}

var result bool
err = json.NewDecoder(resp.Body).Decode(&result)
if err != nil {
return false, err
}

return result, nil
}

func (u *Userli) call(url string) (*http.Response, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
Expand Down
50 changes: 50 additions & 0 deletions userli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,56 @@ func (s *UserliTestSuite) TestGetDomain() {
})
}

func (s *UserliTestSuite) TestGetMailbox() {
s.Run("success", func() {
gock.New("http://localhost:8000").
Get("/api/postfix/mailbox/[email protected]").
MatchHeader("Authentication", "Bearer insecure").
MatchHeader("Accept", "application/json").
MatchHeader("Content-Type", "application/json").
MatchHeader("User-Agent", "userli-postfix-adapter").
Reply(200).
JSON("true")

active, err := s.userli.GetMailbox("[email protected]")
s.NoError(err)
s.True(active)
s.True(gock.IsDone())
})

s.Run("not found", func() {
gock.New("http://localhost:8000").
Get("/api/postfix/mailbox/[email protected]").
MatchHeader("Authentication", "Bearer insecure").
MatchHeader("Accept", "application/json").
MatchHeader("Content-Type", "application/json").
MatchHeader("User-Agent", "userli-postfix-adapter").
Reply(200).
JSON("false")

active, err := s.userli.GetMailbox("[email protected]")
s.NoError(err)
s.False(active)
s.True(gock.IsDone())
})

s.Run("error", func() {
gock.New("http://localhost:8000").
Get("/api/postfix/mailbox/[email protected]").
MatchHeader("Authentication", "Bearer insecure").
MatchHeader("Accept", "application/json").
MatchHeader("Content-Type", "application/json").
MatchHeader("User-Agent", "userli-postfix-adapter").
Reply(500).
JSON(map[string]string{"error": "internal server error"})

active, err := s.userli.GetMailbox("[email protected]")
s.Error(err)
s.False(active)
s.True(gock.IsDone())
})
}

func TestUserl(t *testing.T) {
suite.Run(t, new(UserliTestSuite))
}

0 comments on commit 0b1fc45

Please sign in to comment.