forked from Silverfish/proton-bridge
Import/Export backend
This commit is contained in:
267
test/transfer_checks_test.go
Normal file
267
test/transfer_checks_test.go
Normal file
@ -0,0 +1,267 @@
|
||||
// Copyright (c) 2020 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package tests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/mail"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cucumber/godog"
|
||||
"github.com/cucumber/godog/gherkin"
|
||||
"github.com/emersion/go-mbox"
|
||||
"github.com/emersion/go-message"
|
||||
"github.com/pkg/errors"
|
||||
a "github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TransferChecksFeatureContext(s *godog.Suite) {
|
||||
s.Step(`^progress result is "([^"]*)"$`, progressFinishedWith)
|
||||
s.Step(`^transfer exported (\d+) messages$`, transferExportedNumberOfMessages)
|
||||
s.Step(`^transfer imported (\d+) messages$`, transferImportedNumberOfMessages)
|
||||
s.Step(`^transfer failed for (\d+) messages$`, transferFailedForNumberOfMessages)
|
||||
s.Step(`^transfer exported messages$`, transferExportedMessages)
|
||||
s.Step(`^exported messages match the original ones$`, exportedMessagesMatchTheOriginalOnes)
|
||||
}
|
||||
|
||||
func progressFinishedWith(wantResponse string) error {
|
||||
progress := ctx.GetTransferProgress()
|
||||
// Wait till transport is finished.
|
||||
for range progress.GetUpdateChannel() {
|
||||
}
|
||||
|
||||
err := progress.GetFatalError()
|
||||
if wantResponse == "OK" {
|
||||
a.NoError(ctx.GetTestingT(), err)
|
||||
} else {
|
||||
a.EqualError(ctx.GetTestingT(), err, wantResponse)
|
||||
}
|
||||
return ctx.GetTestingError()
|
||||
}
|
||||
|
||||
func transferExportedNumberOfMessages(wantCount int) error {
|
||||
progress := ctx.GetTransferProgress()
|
||||
_, _, exported, _, _ := progress.GetCounts() //nolint[dogsled]
|
||||
a.Equal(ctx.GetTestingT(), uint(wantCount), exported)
|
||||
return ctx.GetTestingError()
|
||||
}
|
||||
|
||||
func transferImportedNumberOfMessages(wantCount int) error {
|
||||
progress := ctx.GetTransferProgress()
|
||||
_, imported, _, _, _ := progress.GetCounts() //nolint[dogsled]
|
||||
a.Equal(ctx.GetTestingT(), uint(wantCount), imported)
|
||||
return ctx.GetTestingError()
|
||||
}
|
||||
|
||||
func transferFailedForNumberOfMessages(wantCount int) error {
|
||||
progress := ctx.GetTransferProgress()
|
||||
failedMessages := progress.GetFailedMessages()
|
||||
a.Equal(ctx.GetTestingT(), wantCount, len(failedMessages), "failed messages: %v", failedMessages)
|
||||
return ctx.GetTestingError()
|
||||
}
|
||||
|
||||
func transferExportedMessages(messages *gherkin.DataTable) error {
|
||||
expectedMessages := map[string][]MessageAttributes{}
|
||||
|
||||
head := messages.Rows[0].Cells
|
||||
for _, row := range messages.Rows[1:] {
|
||||
folder := ""
|
||||
msg := MessageAttributes{}
|
||||
|
||||
for n, cell := range row.Cells {
|
||||
switch head[n].Value {
|
||||
case "folder":
|
||||
folder = cell.Value
|
||||
case "subject":
|
||||
msg.subject = cell.Value
|
||||
case "from":
|
||||
msg.from = cell.Value
|
||||
case "to":
|
||||
msg.to = []string{cell.Value}
|
||||
case "time":
|
||||
date, err := time.Parse(timeFormat, cell.Value)
|
||||
if err != nil {
|
||||
return internalError(err, "failed to parse time")
|
||||
}
|
||||
msg.date = date.Unix()
|
||||
default:
|
||||
return fmt.Errorf("unexpected column name: %s", head[n].Value)
|
||||
}
|
||||
}
|
||||
|
||||
expectedMessages[folder] = append(expectedMessages[folder], msg)
|
||||
sort.Sort(BySubject(expectedMessages[folder]))
|
||||
}
|
||||
|
||||
exportRoot := ctx.GetTransferLocalRootForExport()
|
||||
exportedMessages, err := readMessages(exportRoot)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "scanning exported messages")
|
||||
}
|
||||
|
||||
a.Equal(ctx.GetTestingT(), expectedMessages, exportedMessages)
|
||||
return ctx.GetTestingError()
|
||||
}
|
||||
|
||||
func exportedMessagesMatchTheOriginalOnes() error {
|
||||
importRoot := ctx.GetTransferLocalRootForImport()
|
||||
exportRoot := ctx.GetTransferLocalRootForExport()
|
||||
|
||||
importMessages, err := readMessages(importRoot)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "scanning messages for import")
|
||||
}
|
||||
exportMessages, err := readMessages(exportRoot)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "scanning exported messages")
|
||||
}
|
||||
delete(exportMessages, "All Mail") // Ignore All Mail.
|
||||
|
||||
a.Equal(ctx.GetTestingT(), importMessages, exportMessages)
|
||||
return ctx.GetTestingError()
|
||||
}
|
||||
|
||||
func readMessages(root string) (map[string][]MessageAttributes, error) {
|
||||
files, err := ioutil.ReadDir(root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
messagesPerLabel := map[string][]MessageAttributes{}
|
||||
for _, file := range files {
|
||||
if !file.IsDir() {
|
||||
fileReader, err := os.Open(filepath.Join(root, file.Name()))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "opening file")
|
||||
}
|
||||
|
||||
if filepath.Ext(file.Name()) == ".eml" {
|
||||
label := filepath.Base(root)
|
||||
msg, err := readMessageAttributes(fileReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
messagesPerLabel[label] = append(messagesPerLabel[label], msg)
|
||||
sort.Sort(BySubject(messagesPerLabel[label]))
|
||||
} else if filepath.Ext(file.Name()) == ".mbox" {
|
||||
label := strings.TrimSuffix(file.Name(), ".mbox")
|
||||
mboxReader := mbox.NewReader(fileReader)
|
||||
for {
|
||||
msgReader, err := mboxReader.NextMessage()
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
return nil, errors.Wrap(err, "reading next message")
|
||||
}
|
||||
msg, err := readMessageAttributes(msgReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
messagesPerLabel[label] = append(messagesPerLabel[label], msg)
|
||||
}
|
||||
sort.Sort(BySubject(messagesPerLabel[label]))
|
||||
}
|
||||
} else {
|
||||
subfolderRoot := filepath.Join(root, file.Name())
|
||||
subfolderMessagesPerLabel, err := readMessages(subfolderRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for key, value := range subfolderMessagesPerLabel {
|
||||
messagesPerLabel[key] = append(messagesPerLabel[key], value...)
|
||||
sort.Sort(BySubject(messagesPerLabel[key]))
|
||||
}
|
||||
}
|
||||
}
|
||||
return messagesPerLabel, nil
|
||||
}
|
||||
|
||||
type MessageAttributes struct {
|
||||
subject string
|
||||
from string
|
||||
to []string
|
||||
date int64
|
||||
}
|
||||
|
||||
func readMessageAttributes(fileReader io.Reader) (MessageAttributes, error) {
|
||||
entity, err := message.Read(fileReader)
|
||||
if err != nil {
|
||||
return MessageAttributes{}, errors.Wrap(err, "reading file")
|
||||
}
|
||||
date, err := parseTime(entity.Header.Get("date"))
|
||||
if err != nil {
|
||||
return MessageAttributes{}, errors.Wrap(err, "parsing date")
|
||||
}
|
||||
from, err := parseAddress(entity.Header.Get("from"))
|
||||
if err != nil {
|
||||
return MessageAttributes{}, errors.Wrap(err, "parsing from")
|
||||
}
|
||||
to, err := parseAddresses(entity.Header.Get("to"))
|
||||
if err != nil {
|
||||
return MessageAttributes{}, errors.Wrap(err, "parsing to")
|
||||
}
|
||||
return MessageAttributes{
|
||||
subject: entity.Header.Get("subject"),
|
||||
from: from,
|
||||
to: to,
|
||||
date: date.Unix(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseTime(input string) (time.Time, error) {
|
||||
for _, format := range []string{time.RFC1123, time.RFC1123Z} {
|
||||
t, err := time.Parse(format, input)
|
||||
if err == nil {
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
return time.Time{}, errors.New("Unrecognized time format")
|
||||
}
|
||||
|
||||
func parseAddresses(input string) ([]string, error) {
|
||||
addresses, err := mail.ParseAddressList(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := []string{}
|
||||
for _, address := range addresses {
|
||||
result = append(result, address.Address)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func parseAddress(input string) (string, error) {
|
||||
address, err := mail.ParseAddress(input)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return address.Address, nil
|
||||
}
|
||||
|
||||
// BySubject implements sort.Interface based on the subject field.
|
||||
type BySubject []MessageAttributes
|
||||
|
||||
func (a BySubject) Len() int { return len(a) }
|
||||
func (a BySubject) Less(i, j int) bool { return a[i].subject < a[j].subject }
|
||||
func (a BySubject) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
Reference in New Issue
Block a user