github.com/decred/politeia@v1.4.0/politeiawww/legacy/mail/client_test.go (about) 1 // Copyright (c) 2021 The Decred developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package mail 6 7 import ( 8 "testing" 9 "time" 10 11 "github.com/decred/politeia/politeiawww/legacy/user" 12 "github.com/google/go-cmp/cmp" 13 "github.com/google/uuid" 14 ) 15 16 func TestFilterRecipients(t *testing.T) { 17 // Setup test params 18 var ( 19 rateLimit = 3 20 rateLimitPeriod = 100 * time.Second 21 22 userNoHistory = uuid.New() // No email history yet 23 userUnderLimit = uuid.New() // Under rate limit by more than 1 24 userNearLimit = uuid.New() // Under rate limit by 1 25 userAtLimit = uuid.New() // At rate limit 26 27 // userAtLimitExpired is a user that is at the email rate limit 28 // but the emails were sent in a previous rate limit period, 29 // meaning the user's email history should be reset and they 30 // should continue to receive emails. 31 userAtLimitExpired = uuid.New() 32 33 emailNoHistory = "no_history@email.com" 34 emailUnderLimit = "under_limit@email.com" 35 emailNearLimit = "near_limit@email.com" 36 emailAtLimit = "at_limit@email.com" 37 emailAtLimitExpired = "at_limit_expired@email.com" 38 39 // The following timestamps are within the current rate limit 40 // period. 41 now = time.Now() 42 time1 = now.Unix() - 1 // 1 second in the past 43 time2 = now.Unix() - 2 // 2 seconds in the past 44 time3 = now.Unix() - 3 // 3 seconds in the past 45 46 // The following timestamps are expired, meaning they occurred in 47 // a previous rate limit period and should not be counted as part 48 // of the current rate limit period. 49 expired = now.Add(-rateLimitPeriod) 50 expired1 = expired.Unix() - 1 // 1 second expired 51 expired2 = expired.Unix() - 2 // 2 seconds expired 52 expired3 = expired.Unix() - 3 // 3 seconds expired 53 54 // histories contains the emails histories that will be seeded 55 // in the MailerDB for the test. 56 histories = map[uuid.UUID]user.EmailHistory{ 57 userUnderLimit: { 58 Timestamps: []int64{time1}, 59 LimitWarningSent: false, 60 }, 61 62 userNearLimit: { 63 Timestamps: []int64{time2, time1}, 64 LimitWarningSent: false, 65 }, 66 67 userAtLimit: { 68 Timestamps: []int64{time3, time2, time1}, 69 LimitWarningSent: true, 70 }, 71 72 userAtLimitExpired: { 73 Timestamps: []int64{expired3, expired2, expired1}, 74 LimitWarningSent: true, 75 }, 76 } 77 78 // emails contains the email list that will be provided to the 79 // filterRecipients function for the test. 80 emails = map[uuid.UUID]string{ 81 userNoHistory: emailNoHistory, 82 userUnderLimit: emailUnderLimit, 83 userNearLimit: emailNearLimit, 84 userAtLimit: emailAtLimit, 85 userAtLimitExpired: emailAtLimitExpired, 86 } 87 ) 88 89 // Setup test mail client 90 c := newTestClient(rateLimit, rateLimitPeriod, histories) 91 92 // Run test 93 fr, err := c.filterRecipients(emails) 94 if err != nil { 95 t.Error(err) 96 } 97 98 // Put the valid and warning lists into maps for easy verification 99 // that a value has been included. 100 var ( 101 valid = make(map[string]struct{}, len(fr.valid)) 102 warning = make(map[string]struct{}, len(fr.warning)) 103 ) 104 for _, v := range fr.valid { 105 valid[v] = struct{}{} 106 } 107 for _, v := range fr.warning { 108 warning[v] = struct{}{} 109 } 110 111 // Verify valid emails list. This should contain the users: 112 // noHistory, underLimit, nearLimit, atLimitExpired. 113 _, ok := valid[emailNoHistory] 114 if !ok { 115 t.Errorf("user with no email history was not found in the "+ 116 "valid emails list: %v", fr.valid) 117 } 118 119 _, ok = valid[emailUnderLimit] 120 if !ok { 121 t.Errorf("user with email history under the rate limit was "+ 122 "not found in the valid emails list: %v", fr.valid) 123 } 124 125 _, ok = valid[emailNearLimit] 126 if !ok { 127 t.Errorf("user with email history under the rate limit by 1 "+ 128 "was not found in the valid emails list: %v", fr.valid) 129 } 130 131 _, ok = valid[emailAtLimitExpired] 132 if !ok { 133 t.Errorf("user with email history at the rate limit but expired "+ 134 "was not found in the valid emails list: %v", fr.valid) 135 } 136 137 if len(fr.valid) != 4 { 138 t.Errorf("valid emails list length want 4, got %v: %v", 139 len(fr.valid), fr.valid) 140 } 141 142 // Verify warning emails. The only user that hit the rate limit 143 // this invocation and thus should be in the warning emails list 144 // is the nearLimit user. 145 _, ok = warning[emailNearLimit] 146 switch { 147 case !ok: 148 t.Errorf("user that hit the rate limit was not found in the "+ 149 "warning emails list: %v", fr.warning) 150 151 case len(fr.warning) != 1: 152 t.Errorf("warning emails list length want 1, got %v: %v", 153 len(fr.warning), fr.warning) 154 } 155 156 eh, ok := fr.histories[userNoHistory] 157 switch { 158 case !ok: 159 t.Errorf("user with no email history was not found in the " + 160 "histories list") 161 162 case len(eh.Timestamps) != 1: 163 t.Errorf("histories length for user with no email history: "+ 164 "want 1, got %v", len(eh.Timestamps)) 165 166 case eh.LimitWarningSent: 167 t.Errorf("limit warning sent for user with no email history: " + 168 "want false, got true") 169 } 170 171 // Verify returned email history for underLimit user 172 eh, ok = fr.histories[userUnderLimit] 173 switch { 174 case !ok: 175 t.Errorf("user with email history under the rate limit was " + 176 "not found in the histories list") 177 178 case len(eh.Timestamps) != 2: 179 t.Errorf("histories length for user under the rate limit: "+ 180 "want 2, got %v", len(eh.Timestamps)) 181 182 case eh.LimitWarningSent: 183 t.Errorf("limit warning sent for user with email history " + 184 "under the rate limit: want false, got true") 185 } 186 187 // Verify returned email history for nearLimit user 188 eh, ok = fr.histories[userNearLimit] 189 switch { 190 case !ok: 191 t.Errorf("user with email history under the rate limit " + 192 "by one was not found in the histories list") 193 194 case len(eh.Timestamps) != 3: 195 t.Errorf("histories length for user under the rate limit "+ 196 "by one: want 3, got %v", len(eh.Timestamps)) 197 198 case !eh.LimitWarningSent: 199 t.Errorf("limit warning sent for user with email history " + 200 "under the rate limit by one: want true, got false") 201 } 202 203 // Verify returned email history for atLimitExpired user 204 eh, ok = fr.histories[userAtLimitExpired] 205 switch { 206 case !ok: 207 t.Errorf("user with email history at the rate limit but " + 208 "expired was not found in the histories list") 209 210 case len(eh.Timestamps) != 1: 211 t.Errorf("histories length for user under the rate limit: "+ 212 "want 1, got %v", len(eh.Timestamps)) 213 214 case eh.LimitWarningSent: 215 t.Errorf("limit warning sent for user with email history " + 216 "at the rate limit but expired: want false, got true") 217 } 218 219 // Verify the filtered histories does not contain unexpected 220 // histories. 221 if len(fr.histories) != 4 { 222 t.Errorf("filtered histories length: want 4, got %v", 223 len(fr.histories)) 224 } 225 } 226 227 func TestFilterTimestamps(t *testing.T) { 228 // Timestamps that are expired based on the default rate limit period, 229 // and that must not be contained on the output if passed as input. 230 minus24h := time.Now().Add(-(24 * time.Hour)).Unix() 231 minus26h := time.Now().Add(-(26 * time.Hour)).Unix() 232 minus28h := time.Now().Add(-(28 * time.Hour)).Unix() 233 minus36h := time.Now().Add(-(36 * time.Hour)).Unix() 234 minus42h := time.Now().Add(-(42 * time.Hour)).Unix() 235 236 // Timestamps that are still valid and within the default rate limit period, 237 // and that must be contained on the output if passed as input. 238 minus12h := time.Now().Add(-(12 * time.Hour)).Unix() 239 minus14h := time.Now().Add(-(14 * time.Hour)).Unix() 240 minus16h := time.Now().Add(-(16 * time.Hour)).Unix() 241 242 // Setup test cases 243 var tests = []struct { 244 name string 245 in []int64 246 wantOut []int64 247 }{ 248 { 249 "remove all timestamps", 250 []int64{minus24h, minus36h, minus42h}, 251 []int64{}, 252 }, 253 { 254 "remove stale timestamps", 255 []int64{minus12h, minus26h, minus28h}, 256 []int64{minus12h}, 257 }, 258 { 259 "no timestamps to remove", 260 []int64{minus12h, minus14h, minus16h}, 261 []int64{minus12h, minus14h, minus16h}, 262 }, 263 } 264 265 for _, v := range tests { 266 t.Run(v.name, func(t *testing.T) { 267 out := filterTimestamps(v.in, defaultRateLimitPeriod) 268 269 // Verify if the function output matches the expected output. 270 diff := cmp.Diff(out, v.wantOut) 271 if diff != "" { 272 t.Errorf("got/want diff: \n%v", diff) 273 } 274 }) 275 } 276 } 277 278 // newTestClient returns a new client that is setup for testing. The caller can 279 // optionally provide a list of email histories to seed the testMailerDB with 280 // on intialization. 281 func newTestClient(rateLimit int, rateLimitPeriod time.Duration, histories map[uuid.UUID]user.EmailHistory) *client { 282 return &client{ 283 smtp: nil, 284 mailName: "test", 285 mailAddress: "test@email.com", 286 mailerDB: user.NewTestMailerDB(histories), 287 disabled: false, 288 rateLimit: rateLimit, 289 rateLimitPeriod: rateLimitPeriod, 290 } 291 }