gophish/models/campaign_test.go
Jordan Wright 8162a80cb1
Improve the Performance of Sending Emails (#1753)
Adding the ability to cache campaigns on maillogs to greatly improve generation time (and, by extension, sending speed and memory usage).
2020-02-29 20:19:54 -06:00

337 lines
9.7 KiB
Go

package models
import (
"fmt"
"testing"
"time"
check "gopkg.in/check.v1"
)
func (s *ModelsSuite) TestGenerateSendDate(c *check.C) {
campaign := s.createCampaignDependencies(c)
// Test that if no launch date is provided, the campaign's creation date
// is used.
err := PostCampaign(&campaign, campaign.UserId)
c.Assert(err, check.Equals, nil)
c.Assert(campaign.LaunchDate, check.Equals, campaign.CreatedDate)
// For comparing the dates, we need to fetch the campaign again. This is
// to solve an issue where the campaign object right now has time down to
// the microsecond, while in MySQL it's rounded down to the second.
campaign, _ = GetCampaign(campaign.Id, campaign.UserId)
ms, err := GetMailLogsByCampaign(campaign.Id)
c.Assert(err, check.Equals, nil)
for _, m := range ms {
c.Assert(m.SendDate, check.Equals, campaign.CreatedDate)
}
// Test that if no send date is provided, all the emails are sent at the
// campaign's launch date
campaign = s.createCampaignDependencies(c)
campaign.LaunchDate = time.Now().UTC()
err = PostCampaign(&campaign, campaign.UserId)
c.Assert(err, check.Equals, nil)
campaign, _ = GetCampaign(campaign.Id, campaign.UserId)
ms, err = GetMailLogsByCampaign(campaign.Id)
c.Assert(err, check.Equals, nil)
for _, m := range ms {
c.Assert(m.SendDate, check.Equals, campaign.LaunchDate)
}
// Finally, test that if a send date is provided, the emails are staggered
// correctly.
campaign = s.createCampaignDependencies(c)
campaign.LaunchDate = time.Now().UTC()
campaign.SendByDate = campaign.LaunchDate.Add(2 * time.Minute)
err = PostCampaign(&campaign, campaign.UserId)
c.Assert(err, check.Equals, nil)
campaign, _ = GetCampaign(campaign.Id, campaign.UserId)
ms, err = GetMailLogsByCampaign(campaign.Id)
c.Assert(err, check.Equals, nil)
sendingOffset := 2 / float64(len(ms))
for i, m := range ms {
expectedOffset := int(sendingOffset * float64(i))
expectedDate := campaign.LaunchDate.Add(time.Duration(expectedOffset) * time.Minute)
c.Assert(m.SendDate, check.Equals, expectedDate)
}
}
func (s *ModelsSuite) TestCampaignDateValidation(c *check.C) {
campaign := s.createCampaignDependencies(c)
// If both are zero, then the campaign should start immediately with no
// send by date
err := campaign.Validate()
c.Assert(err, check.Equals, nil)
// If the launch date is specified, then the send date is optional
campaign = s.createCampaignDependencies(c)
campaign.LaunchDate = time.Now().UTC()
err = campaign.Validate()
c.Assert(err, check.Equals, nil)
// If the send date is greater than the launch date, then there's no
//problem
campaign = s.createCampaignDependencies(c)
campaign.LaunchDate = time.Now().UTC()
campaign.SendByDate = campaign.LaunchDate.Add(1 * time.Minute)
err = campaign.Validate()
c.Assert(err, check.Equals, nil)
// If the send date is less than the launch date, then there's an issue
campaign = s.createCampaignDependencies(c)
campaign.LaunchDate = time.Now().UTC()
campaign.SendByDate = campaign.LaunchDate.Add(-1 * time.Minute)
err = campaign.Validate()
c.Assert(err, check.Equals, ErrInvalidSendByDate)
}
func (s *ModelsSuite) TestLaunchCampaignMaillogStatus(c *check.C) {
// For the first test, ensure that campaigns created with the zero date
// (and therefore are set to launch immediately) have maillogs that are
// locked to prevent race conditions.
campaign := s.createCampaign(c)
ms, err := GetMailLogsByCampaign(campaign.Id)
c.Assert(err, check.Equals, nil)
for _, m := range ms {
c.Assert(m.Processing, check.Equals, true)
}
// Next, verify that campaigns scheduled in the future do not lock the
// maillogs so that they can be picked up by the background worker.
campaign = s.createCampaignDependencies(c)
campaign.Name = "New Campaign"
campaign.LaunchDate = time.Now().Add(1 * time.Hour)
c.Assert(PostCampaign(&campaign, campaign.UserId), check.Equals, nil)
ms, err = GetMailLogsByCampaign(campaign.Id)
c.Assert(err, check.Equals, nil)
for _, m := range ms {
c.Assert(m.Processing, check.Equals, false)
}
}
func (s *ModelsSuite) TestDeleteCampaignAlsoDeletesMailLogs(c *check.C) {
campaign := s.createCampaign(c)
ms, err := GetMailLogsByCampaign(campaign.Id)
c.Assert(err, check.Equals, nil)
c.Assert(len(ms), check.Equals, len(campaign.Results))
err = DeleteCampaign(campaign.Id)
c.Assert(err, check.Equals, nil)
ms, err = GetMailLogsByCampaign(campaign.Id)
c.Assert(err, check.Equals, nil)
c.Assert(len(ms), check.Equals, 0)
}
func (s *ModelsSuite) TestCompleteCampaignAlsoDeletesMailLogs(c *check.C) {
campaign := s.createCampaign(c)
ms, err := GetMailLogsByCampaign(campaign.Id)
c.Assert(err, check.Equals, nil)
c.Assert(len(ms), check.Equals, len(campaign.Results))
err = CompleteCampaign(campaign.Id, campaign.UserId)
c.Assert(err, check.Equals, nil)
ms, err = GetMailLogsByCampaign(campaign.Id)
c.Assert(err, check.Equals, nil)
c.Assert(len(ms), check.Equals, 0)
}
func (s *ModelsSuite) TestCampaignGetResults(c *check.C) {
campaign := s.createCampaign(c)
got, err := GetCampaign(campaign.Id, campaign.UserId)
c.Assert(err, check.Equals, nil)
c.Assert(len(campaign.Results), check.Equals, len(got.Results))
}
func setupCampaignDependencies(b *testing.B, size int) {
group := Group{Name: "Test Group"}
// Create a large group of 5000 members
for i := 0; i < size; i++ {
group.Targets = append(group.Targets, Target{BaseRecipient: BaseRecipient{Email: fmt.Sprintf("test%d@example.com", i), FirstName: "User", LastName: fmt.Sprintf("%d", i)}})
}
group.UserId = 1
err := PostGroup(&group)
if err != nil {
b.Fatalf("error posting group: %v", err)
}
// Add a template
template := Template{Name: "Test Template"}
template.Subject = "{{.RId}} - Subject"
template.Text = "{{.RId}} - Text"
template.HTML = "{{.RId}} - HTML"
template.UserId = 1
err = PostTemplate(&template)
if err != nil {
b.Fatalf("error posting template: %v", err)
}
// Add a landing page
p := Page{Name: "Test Page"}
p.HTML = "<html>Test</html>"
p.UserId = 1
err = PostPage(&p)
if err != nil {
b.Fatalf("error posting page: %v", err)
}
// Add a sending profile
smtp := SMTP{Name: "Test Page"}
smtp.UserId = 1
smtp.Host = "example.com"
smtp.FromAddress = "test@test.com"
err = PostSMTP(&smtp)
if err != nil {
b.Fatalf("error posting smtp: %v", err)
}
}
// setupCampaign sets up the campaign dependencies as well as posting the
// actual campaign
func setupCampaign(b *testing.B, size int) Campaign {
setupCampaignDependencies(b, size)
campaign := Campaign{Name: "Test campaign"}
campaign.UserId = 1
campaign.Template = Template{Name: "Test Template"}
campaign.Page = Page{Name: "Test Page"}
campaign.SMTP = SMTP{Name: "Test Page"}
campaign.Groups = []Group{Group{Name: "Test Group"}}
PostCampaign(&campaign, 1)
return campaign
}
func BenchmarkCampaign100(b *testing.B) {
setupBenchmark(b)
setupCampaignDependencies(b, 100)
b.ResetTimer()
for i := 0; i < b.N; i++ {
campaign := Campaign{Name: "Test campaign"}
campaign.UserId = 1
campaign.Template = Template{Name: "Test Template"}
campaign.Page = Page{Name: "Test Page"}
campaign.SMTP = SMTP{Name: "Test Page"}
campaign.Groups = []Group{Group{Name: "Test Group"}}
b.StartTimer()
err := PostCampaign(&campaign, 1)
if err != nil {
b.Fatalf("error posting campaign: %v", err)
}
b.StopTimer()
db.Delete(Result{})
db.Delete(MailLog{})
db.Delete(Campaign{})
}
tearDownBenchmark(b)
}
func BenchmarkCampaign1000(b *testing.B) {
setupBenchmark(b)
setupCampaignDependencies(b, 1000)
b.ResetTimer()
for i := 0; i < b.N; i++ {
campaign := Campaign{Name: "Test campaign"}
campaign.UserId = 1
campaign.Template = Template{Name: "Test Template"}
campaign.Page = Page{Name: "Test Page"}
campaign.SMTP = SMTP{Name: "Test Page"}
campaign.Groups = []Group{Group{Name: "Test Group"}}
b.StartTimer()
err := PostCampaign(&campaign, 1)
if err != nil {
b.Fatalf("error posting campaign: %v", err)
}
b.StopTimer()
db.Delete(Result{})
db.Delete(MailLog{})
db.Delete(Campaign{})
}
tearDownBenchmark(b)
}
func BenchmarkCampaign10000(b *testing.B) {
setupBenchmark(b)
setupCampaignDependencies(b, 10000)
b.ResetTimer()
for i := 0; i < b.N; i++ {
campaign := Campaign{Name: "Test campaign"}
campaign.UserId = 1
campaign.Template = Template{Name: "Test Template"}
campaign.Page = Page{Name: "Test Page"}
campaign.SMTP = SMTP{Name: "Test Page"}
campaign.Groups = []Group{Group{Name: "Test Group"}}
b.StartTimer()
err := PostCampaign(&campaign, 1)
if err != nil {
b.Fatalf("error posting campaign: %v", err)
}
b.StopTimer()
db.Delete(Result{})
db.Delete(MailLog{})
db.Delete(Campaign{})
}
tearDownBenchmark(b)
}
func BenchmarkGetCampaign100(b *testing.B) {
setupBenchmark(b)
campaign := setupCampaign(b, 100)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := GetCampaign(campaign.Id, campaign.UserId)
if err != nil {
b.Fatalf("error getting campaign: %v", err)
}
}
tearDownBenchmark(b)
}
func BenchmarkGetCampaign1000(b *testing.B) {
setupBenchmark(b)
campaign := setupCampaign(b, 1000)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := GetCampaign(campaign.Id, campaign.UserId)
if err != nil {
b.Fatalf("error getting campaign: %v", err)
}
}
tearDownBenchmark(b)
}
func BenchmarkGetCampaign5000(b *testing.B) {
setupBenchmark(b)
campaign := setupCampaign(b, 5000)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := GetCampaign(campaign.Id, campaign.UserId)
if err != nil {
b.Fatalf("error getting campaign: %v", err)
}
}
tearDownBenchmark(b)
}
func BenchmarkGetCampaign10000(b *testing.B) {
setupBenchmark(b)
campaign := setupCampaign(b, 10000)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := GetCampaign(campaign.Id, campaign.UserId)
if err != nil {
b.Fatalf("error getting campaign: %v", err)
}
}
tearDownBenchmark(b)
}