auth
auth/profile_test.go
Update to use a generic event emitter. Rather can creating a purpose-built event emitter for each and every event we need to emit (I'm looking at you, login verification event) which is _downright silly_, we're now using a generic event publisher that's based on saying "HEY A MODEL UPDATED". This means we need to change all our setup code in authd to use events.NewNSQPublisher or events.NewStdoutPublisher instead of our homegrown solutions. Which also means updating our config to take an events.Publisher instead of our LoginVerificationNotifier (blergh). Our Context also now uses an events.Publisher instead of a LoginVerificationNotifier. Party all around! We also replaced our SendLoginVerification helper method on Context with a SendModelEvent helper method on Context, which is just a light wrapper around events.PublishModelEvent. Of course, all this means we need to update our email_verification listener to listen to the correct channel (based on the model we want updates about) and filter down to a Created action or our new custom action for "the customer wants their verification resent", which I'm OK making a special case and not generic, because c'mon. But we had a subtle change to all our constants, some of which are unofficial constants now. I'm unsure how I feel about this. We also updated our email_verification listener so that we're unmarshalling to a custom loginEvent, which is just an events.Event that overwrites the Data property to be an auth.Login instance. This is to make sure we don't need to wrangle a map[string]interface{}, which is no fun. I'm also OK with special-casing like this, because it's 1) a tiny amount of code, 2) properly utilising composition, and 3) the only way I can think of to cleanly accomplish what I want. I also added a note about GetLogin's deficient handling of logins, namely that it doesn't recognise admins and return Verification codes to them, which would be a useful property for internal tools to take advantage of. Ah well. I updated the Profile and Login implementations so they're now event.Model instances, mainly by just exporting some strings from them through getters that will let us automatically build an Event from them. This lets us use the PublishModelEvent helper. I updated our CreateProfileHandler to properly mangle the login Verification property, and to fire off the ActionCreated events for the new Login and the new Profile. I updated our GetLoginHandler and UpdateLoginHandler to properly mangle the loginVerification property. God that's annoying. :-/ You'll note I didn't start publishing the events.ActionUpdated or events.ActionDeleted events for Profiles or Logins yet, and didn't bother publishing any events for literally any other type. That's because I'm a lazy piece of crap and will end up publishing them when I absolutely have to. Part of that is because if a channel isn't created/being read for a topic, the messages will just stack up in NSQ, and I don't want that. But mostly I'm lazy. Finally, I got to delete the entire profile_verification.go file, because we're no longer special-casing that. Hooray!
| paddy@38 | 1 package auth |
| paddy@38 | 2 |
| paddy@38 | 3 import ( |
| paddy@38 | 4 "fmt" |
| paddy@153 | 5 "os" |
| paddy@38 | 6 "testing" |
| paddy@38 | 7 "time" |
| paddy@38 | 8 |
| paddy@107 | 9 "code.secondbit.org/uuid.hg" |
| paddy@38 | 10 ) |
| paddy@38 | 11 |
| paddy@38 | 12 const ( |
| paddy@38 | 13 profileChangeName = 1 << iota |
| paddy@38 | 14 profileChangePassphrase |
| paddy@38 | 15 profileChangeIterations |
| paddy@38 | 16 profileChangeSalt |
| paddy@38 | 17 profileChangePassphraseScheme |
| paddy@38 | 18 profileChangeCompromised |
| paddy@38 | 19 profileChangeLockedUntil |
| paddy@38 | 20 profileChangePassphraseReset |
| paddy@38 | 21 profileChangePassphraseResetCreated |
| paddy@38 | 22 profileChangeLastSeen |
| paddy@38 | 23 ) |
| paddy@38 | 24 |
| paddy@149 | 25 func init() { |
| paddy@153 | 26 if os.Getenv("PG_TEST_DB") != "" { |
| paddy@153 | 27 p, err := NewPostgres(os.Getenv("PG_TEST_DB")) |
| paddy@153 | 28 if err != nil { |
| paddy@153 | 29 panic(err) |
| paddy@153 | 30 } |
| paddy@149 | 31 profileStores = append(profileStores, &p) |
| paddy@149 | 32 } |
| paddy@149 | 33 } |
| paddy@149 | 34 |
| paddy@57 | 35 var profileStores = []profileStore{NewMemstore()} |
| paddy@38 | 36 |
| paddy@38 | 37 func compareProfiles(profile1, profile2 Profile) (success bool, field string, val1, val2 interface{}) { |
| paddy@38 | 38 if !profile1.ID.Equal(profile2.ID) { |
| paddy@38 | 39 return false, "ID", profile1.ID, profile2.ID |
| paddy@38 | 40 } |
| paddy@38 | 41 if profile1.Name != profile2.Name { |
| paddy@38 | 42 return false, "name", profile1.Name, profile2.Name |
| paddy@38 | 43 } |
| paddy@38 | 44 if profile1.Passphrase != profile2.Passphrase { |
| paddy@38 | 45 return false, "passphrase", profile1.Passphrase, profile2.Passphrase |
| paddy@38 | 46 } |
| paddy@38 | 47 if profile1.Iterations != profile2.Iterations { |
| paddy@38 | 48 return false, "iterations", profile1.Iterations, profile2.Iterations |
| paddy@38 | 49 } |
| paddy@38 | 50 if profile1.Salt != profile2.Salt { |
| paddy@38 | 51 return false, "salt", profile1.Salt, profile2.Salt |
| paddy@38 | 52 } |
| paddy@38 | 53 if profile1.PassphraseScheme != profile2.PassphraseScheme { |
| paddy@38 | 54 return false, "passphrase scheme", profile1.PassphraseScheme, profile2.PassphraseScheme |
| paddy@38 | 55 } |
| paddy@38 | 56 if profile1.Compromised != profile2.Compromised { |
| paddy@38 | 57 return false, "compromised", profile1.Compromised, profile2.Compromised |
| paddy@38 | 58 } |
| paddy@38 | 59 if !profile1.LockedUntil.Equal(profile2.LockedUntil) { |
| paddy@38 | 60 return false, "locked until", profile1.LockedUntil, profile2.LockedUntil |
| paddy@38 | 61 } |
| paddy@38 | 62 if profile1.PassphraseReset != profile2.PassphraseReset { |
| paddy@38 | 63 return false, "passphrase reset", profile1.PassphraseReset, profile2.PassphraseReset |
| paddy@38 | 64 } |
| paddy@38 | 65 if !profile1.PassphraseResetCreated.Equal(profile2.PassphraseResetCreated) { |
| paddy@38 | 66 return false, "passphrase reset created", profile1.PassphraseResetCreated, profile2.PassphraseResetCreated |
| paddy@38 | 67 } |
| paddy@38 | 68 if !profile1.Created.Equal(profile2.Created) { |
| paddy@38 | 69 return false, "created", profile1.Created, profile2.Created |
| paddy@38 | 70 } |
| paddy@38 | 71 if !profile1.LastSeen.Equal(profile2.LastSeen) { |
| paddy@38 | 72 return false, "last seen", profile1.LastSeen, profile2.LastSeen |
| paddy@38 | 73 } |
| paddy@38 | 74 return true, "", nil, nil |
| paddy@38 | 75 } |
| paddy@38 | 76 |
| paddy@46 | 77 func compareLogins(login1, login2 Login) (success bool, field string, val1, val2 interface{}) { |
| paddy@46 | 78 if login1.Type != login2.Type { |
| paddy@46 | 79 return false, "Type", login1.Type, login2.Type |
| paddy@46 | 80 } |
| paddy@46 | 81 if login1.Value != login2.Value { |
| paddy@46 | 82 return false, "Value", login1.Value, login2.Value |
| paddy@46 | 83 } |
| paddy@46 | 84 if !login1.ProfileID.Equal(login2.ProfileID) { |
| paddy@46 | 85 return false, "ProfileID", login1.ProfileID, login2.ProfileID |
| paddy@46 | 86 } |
| paddy@46 | 87 if !login1.Created.Equal(login2.Created) { |
| paddy@46 | 88 return false, "Created", login1.Created, login2.Created |
| paddy@46 | 89 } |
| paddy@46 | 90 if !login1.LastUsed.Equal(login2.LastUsed) { |
| paddy@46 | 91 return false, "LastUsed", login1.LastUsed, login2.LastUsed |
| paddy@46 | 92 } |
| paddy@172 | 93 if login1.Verification != login2.Verification { |
| paddy@172 | 94 return false, "Verification", login1.Verification, login2.Verification |
| paddy@172 | 95 } |
| paddy@172 | 96 if login1.Verified != login2.Verified { |
| paddy@172 | 97 return false, "Verified", login1.Verified, login2.Verified |
| paddy@172 | 98 } |
| paddy@46 | 99 return true, "", nil, nil |
| paddy@46 | 100 } |
| paddy@46 | 101 |
| paddy@38 | 102 func TestProfileStoreSuccess(t *testing.T) { |
| paddy@38 | 103 t.Parallel() |
| paddy@38 | 104 profile := Profile{ |
| paddy@38 | 105 ID: uuid.NewID(), |
| paddy@38 | 106 Name: "name", |
| paddy@38 | 107 Passphrase: "passphrase", |
| paddy@38 | 108 Iterations: 10000, |
| paddy@38 | 109 Salt: "salt", |
| paddy@38 | 110 PassphraseScheme: 1, |
| paddy@38 | 111 Compromised: false, |
| paddy@149 | 112 LockedUntil: time.Now().Add(time.Hour).Round(time.Millisecond), |
| paddy@38 | 113 PassphraseReset: "passphrase reset", |
| paddy@149 | 114 PassphraseResetCreated: time.Now().Round(time.Millisecond), |
| paddy@149 | 115 Created: time.Now().Round(time.Millisecond), |
| paddy@149 | 116 LastSeen: time.Now().Round(time.Millisecond), |
| paddy@38 | 117 } |
| paddy@38 | 118 for _, store := range profileStores { |
| paddy@116 | 119 context := Context{profiles: store} |
| paddy@116 | 120 err := context.SaveProfile(profile) |
| paddy@38 | 121 if err != nil { |
| paddy@38 | 122 t.Errorf("Error saving profile to %T: %s", store, err) |
| paddy@38 | 123 } |
| paddy@116 | 124 err = context.SaveProfile(profile) |
| paddy@38 | 125 if err != ErrProfileAlreadyExists { |
| paddy@38 | 126 t.Errorf("Expected ErrProfileAlreadyExists from %T, got %+v", store, err) |
| paddy@38 | 127 } |
| paddy@116 | 128 retrieved, err := context.GetProfileByID(profile.ID) |
| paddy@38 | 129 if err != nil { |
| paddy@38 | 130 t.Errorf("Error retrieving profile from %T: %s", store, err) |
| paddy@38 | 131 } |
| paddy@38 | 132 match, field, expectation, result := compareProfiles(profile, retrieved) |
| paddy@38 | 133 if !match { |
| paddy@38 | 134 t.Errorf("Expected `%v` in the `%s` field of profile retrieved from %T, got `%v`", expectation, field, store, result) |
| paddy@38 | 135 } |
| paddy@161 | 136 err = context.DeleteProfile(profile.ID) |
| paddy@38 | 137 if err != nil { |
| paddy@38 | 138 t.Errorf("Error removing profile from %T: %s", store, err) |
| paddy@38 | 139 } |
| paddy@116 | 140 retrieved, err = context.GetProfileByID(profile.ID) |
| paddy@38 | 141 if err != ErrProfileNotFound { |
| paddy@38 | 142 t.Errorf("Expected ErrProfileNotFound from %T, got %+v and %+v", store, retrieved, err) |
| paddy@38 | 143 } |
| paddy@38 | 144 } |
| paddy@38 | 145 } |
| paddy@38 | 146 |
| paddy@38 | 147 func TestProfileUpdates(t *testing.T) { |
| paddy@38 | 148 t.Parallel() |
| paddy@38 | 149 variations := 1 << 10 |
| paddy@38 | 150 profile := Profile{ |
| paddy@38 | 151 ID: uuid.NewID(), |
| paddy@38 | 152 Name: "name", |
| paddy@38 | 153 Passphrase: "passphrase", |
| paddy@38 | 154 Iterations: 10000, |
| paddy@38 | 155 Salt: "salt", |
| paddy@38 | 156 PassphraseScheme: 1, |
| paddy@38 | 157 Compromised: false, |
| paddy@149 | 158 LockedUntil: time.Now().Add(time.Hour).Round(time.Millisecond), |
| paddy@38 | 159 PassphraseReset: "passphrase reset", |
| paddy@149 | 160 PassphraseResetCreated: time.Now().Round(time.Millisecond), |
| paddy@149 | 161 Created: time.Now().Round(time.Millisecond), |
| paddy@149 | 162 LastSeen: time.Now().Round(time.Millisecond), |
| paddy@38 | 163 } |
| paddy@38 | 164 for i := 0; i < variations; i++ { |
| paddy@38 | 165 var name, passphrase, salt, passphraseReset string |
| paddy@69 | 166 var iterations int |
| paddy@38 | 167 var lockedUntil, passphraseResetCreated, lastSeen time.Time |
| paddy@38 | 168 var passphraseScheme int |
| paddy@38 | 169 var compromised bool |
| paddy@38 | 170 |
| paddy@148 | 171 profile.ID = uuid.NewID() |
| paddy@38 | 172 change := ProfileChange{} |
| paddy@38 | 173 expectation := profile |
| paddy@38 | 174 result := profile |
| paddy@38 | 175 if i&profileChangeName != 0 { |
| paddy@38 | 176 name = fmt.Sprintf("name-%d", i) |
| paddy@38 | 177 change.Name = &name |
| paddy@38 | 178 expectation.Name = name |
| paddy@38 | 179 } |
| paddy@38 | 180 if i&profileChangePassphrase != 0 { |
| paddy@38 | 181 passphrase = fmt.Sprintf("passphrase-%d", i) |
| paddy@38 | 182 change.Passphrase = &passphrase |
| paddy@38 | 183 expectation.Passphrase = passphrase |
| paddy@38 | 184 } |
| paddy@38 | 185 if i&profileChangeIterations != 0 { |
| paddy@69 | 186 iterations = i |
| paddy@38 | 187 change.Iterations = &iterations |
| paddy@38 | 188 expectation.Iterations = iterations |
| paddy@38 | 189 } |
| paddy@38 | 190 if i&profileChangeSalt != 0 { |
| paddy@38 | 191 salt = fmt.Sprintf("salt-%d", i) |
| paddy@38 | 192 change.Salt = &salt |
| paddy@38 | 193 expectation.Salt = salt |
| paddy@38 | 194 } |
| paddy@38 | 195 if i&profileChangePassphraseScheme != 0 { |
| paddy@38 | 196 passphraseScheme = i |
| paddy@38 | 197 change.PassphraseScheme = &passphraseScheme |
| paddy@38 | 198 expectation.PassphraseScheme = passphraseScheme |
| paddy@38 | 199 } |
| paddy@38 | 200 if i&profileChangeCompromised != 0 { |
| paddy@38 | 201 compromised = i%2 != 0 |
| paddy@38 | 202 change.Compromised = &compromised |
| paddy@38 | 203 expectation.Compromised = compromised |
| paddy@38 | 204 } |
| paddy@38 | 205 if i&profileChangeLockedUntil != 0 { |
| paddy@149 | 206 lockedUntil = time.Now().Round(time.Millisecond) |
| paddy@38 | 207 change.LockedUntil = &lockedUntil |
| paddy@38 | 208 expectation.LockedUntil = lockedUntil |
| paddy@38 | 209 } |
| paddy@38 | 210 if i&profileChangePassphraseReset != 0 { |
| paddy@38 | 211 passphraseReset = fmt.Sprintf("passphraseReset-%d", i) |
| paddy@38 | 212 change.PassphraseReset = &passphraseReset |
| paddy@38 | 213 expectation.PassphraseReset = passphraseReset |
| paddy@38 | 214 } |
| paddy@38 | 215 if i&profileChangePassphraseResetCreated != 0 { |
| paddy@149 | 216 passphraseResetCreated = time.Now().Round(time.Millisecond) |
| paddy@38 | 217 change.PassphraseResetCreated = &passphraseResetCreated |
| paddy@38 | 218 expectation.PassphraseResetCreated = passphraseResetCreated |
| paddy@38 | 219 } |
| paddy@38 | 220 if i&profileChangeLastSeen != 0 { |
| paddy@149 | 221 lastSeen = time.Now().Round(time.Millisecond) |
| paddy@38 | 222 change.LastSeen = &lastSeen |
| paddy@38 | 223 expectation.LastSeen = lastSeen |
| paddy@38 | 224 } |
| paddy@38 | 225 result.ApplyChange(change) |
| paddy@38 | 226 match, field, expected, got := compareProfiles(expectation, result) |
| paddy@38 | 227 if !match { |
| paddy@38 | 228 t.Errorf("Expected field `%s` to be `%v`, got `%v`", field, expected, got) |
| paddy@38 | 229 } |
| paddy@38 | 230 for _, store := range profileStores { |
| paddy@116 | 231 context := Context{profiles: store} |
| paddy@116 | 232 err := context.SaveProfile(profile) |
| paddy@38 | 233 if err != nil { |
| paddy@38 | 234 t.Errorf("Error saving profile in %T: %s", store, err) |
| paddy@38 | 235 } |
| paddy@116 | 236 err = context.UpdateProfile(profile.ID, change) |
| paddy@38 | 237 if err != nil { |
| paddy@38 | 238 t.Errorf("Error updating profile in %T: %s", store, err) |
| paddy@38 | 239 } |
| paddy@116 | 240 retrieved, err := context.GetProfileByID(profile.ID) |
| paddy@38 | 241 if err != nil { |
| paddy@38 | 242 t.Errorf("Error getting profile from %T: %s", store, err) |
| paddy@38 | 243 } |
| paddy@38 | 244 match, field, expected, got = compareProfiles(expectation, retrieved) |
| paddy@38 | 245 if !match { |
| paddy@38 | 246 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store) |
| paddy@38 | 247 } |
| paddy@38 | 248 } |
| paddy@38 | 249 } |
| paddy@38 | 250 } |
| paddy@45 | 251 |
| paddy@45 | 252 func TestProfilesUpdates(t *testing.T) { |
| paddy@45 | 253 profile1 := Profile{ |
| paddy@45 | 254 ID: uuid.NewID(), |
| paddy@45 | 255 } |
| paddy@45 | 256 profile2 := Profile{ |
| paddy@45 | 257 ID: uuid.NewID(), |
| paddy@45 | 258 } |
| paddy@45 | 259 profile3 := Profile{ |
| paddy@45 | 260 ID: uuid.NewID(), |
| paddy@45 | 261 } |
| paddy@45 | 262 truth := true |
| paddy@45 | 263 change := BulkProfileChange{ |
| paddy@45 | 264 Compromised: &truth, |
| paddy@45 | 265 } |
| paddy@45 | 266 for _, store := range profileStores { |
| paddy@116 | 267 context := Context{profiles: store} |
| paddy@116 | 268 err := context.SaveProfile(profile1) |
| paddy@45 | 269 if err != nil { |
| paddy@45 | 270 t.Errorf("Error saving profile in %T: %s", store, err) |
| paddy@45 | 271 } |
| paddy@116 | 272 err = context.SaveProfile(profile2) |
| paddy@45 | 273 if err != nil { |
| paddy@45 | 274 t.Errorf("Error saving profile in %T: %s", store, err) |
| paddy@45 | 275 } |
| paddy@116 | 276 err = context.SaveProfile(profile3) |
| paddy@45 | 277 if err != nil { |
| paddy@45 | 278 t.Errorf("Error saving profile in %T: %s", store, err) |
| paddy@45 | 279 } |
| paddy@116 | 280 err = context.UpdateProfiles([]uuid.ID{profile1.ID, profile3.ID}, change) |
| paddy@45 | 281 if err != nil { |
| paddy@45 | 282 t.Errorf("Error updating profile in %T: %s", store, err) |
| paddy@45 | 283 } |
| paddy@45 | 284 profile1.Compromised = truth |
| paddy@45 | 285 profile3.Compromised = truth |
| paddy@116 | 286 retrieved, err := context.GetProfileByID(profile1.ID) |
| paddy@45 | 287 if err != nil { |
| paddy@45 | 288 t.Errorf("Error getting profile from %T: %s", store, err) |
| paddy@45 | 289 } |
| paddy@45 | 290 match, field, expected, got := compareProfiles(profile1, retrieved) |
| paddy@45 | 291 if !match { |
| paddy@45 | 292 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store) |
| paddy@45 | 293 } |
| paddy@116 | 294 retrieved, err = context.GetProfileByID(profile2.ID) |
| paddy@45 | 295 if err != nil { |
| paddy@45 | 296 t.Errorf("Error getting profile from %T: %s", store, err) |
| paddy@45 | 297 } |
| paddy@45 | 298 match, field, expected, got = compareProfiles(profile2, retrieved) |
| paddy@45 | 299 if !match { |
| paddy@45 | 300 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store) |
| paddy@45 | 301 } |
| paddy@116 | 302 retrieved, err = context.GetProfileByID(profile3.ID) |
| paddy@45 | 303 if err != nil { |
| paddy@45 | 304 t.Errorf("Error getting profile from %T: %s", store, err) |
| paddy@45 | 305 } |
| paddy@45 | 306 match, field, expected, got = compareProfiles(profile3, retrieved) |
| paddy@45 | 307 if !match { |
| paddy@45 | 308 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store) |
| paddy@45 | 309 } |
| paddy@45 | 310 } |
| paddy@45 | 311 } |
| paddy@46 | 312 |
| paddy@46 | 313 func TestProfileStoreLoginSuccess(t *testing.T) { |
| paddy@46 | 314 t.Parallel() |
| paddy@46 | 315 login := Login{ |
| paddy@46 | 316 Type: "type", |
| paddy@46 | 317 Value: "value", |
| paddy@46 | 318 ProfileID: uuid.NewID(), |
| paddy@149 | 319 Created: time.Now().Add(-1 * time.Hour).Round(time.Millisecond), |
| paddy@149 | 320 LastUsed: time.Now().Add(-1 * time.Minute).Round(time.Millisecond), |
| paddy@46 | 321 } |
| paddy@46 | 322 for _, store := range profileStores { |
| paddy@116 | 323 context := Context{profiles: store} |
| paddy@116 | 324 err := context.AddLogin(login) |
| paddy@46 | 325 if err != nil { |
| paddy@46 | 326 t.Errorf("Error adding login to %T: %s", store, err) |
| paddy@46 | 327 } |
| paddy@116 | 328 err = context.AddLogin(login) |
| paddy@46 | 329 if err != ErrLoginAlreadyExists { |
| paddy@46 | 330 t.Errorf("Expected ErrLoginAlreadyExists from %T, got %+v", store, err) |
| paddy@46 | 331 } |
| paddy@116 | 332 retrieved, err := context.ListLogins(login.ProfileID, 10, 0) |
| paddy@46 | 333 if err != nil { |
| paddy@46 | 334 t.Errorf("Error retrieving logins from %T: %s", store, err) |
| paddy@46 | 335 } |
| paddy@46 | 336 if len(retrieved) != 1 { |
| paddy@46 | 337 t.Errorf("Expected 1 login result from %T, got %d", store, len(retrieved)) |
| paddy@46 | 338 } |
| paddy@46 | 339 match, field, expectation, result := compareLogins(login, retrieved[0]) |
| paddy@46 | 340 if !match { |
| paddy@46 | 341 t.Errorf("Expected `%v` in the `%s` field of login retrieved from %T, got `%v`", expectation, field, store, result) |
| paddy@46 | 342 } |
| paddy@149 | 343 lastUsed := time.Now().Round(time.Millisecond) |
| paddy@116 | 344 err = context.RecordLoginUse(login.Value, lastUsed) |
| paddy@46 | 345 if err != nil { |
| paddy@46 | 346 t.Errorf("Error recording use of login to %T: %s", store, err) |
| paddy@46 | 347 } |
| paddy@46 | 348 login.LastUsed = lastUsed |
| paddy@116 | 349 retrieved, err = context.ListLogins(login.ProfileID, 10, 0) |
| paddy@46 | 350 if err != nil { |
| paddy@46 | 351 t.Errorf("Error retrieving logins from %T: %s", store, err) |
| paddy@46 | 352 } |
| paddy@46 | 353 if len(retrieved) != 1 { |
| paddy@46 | 354 t.Errorf("Expected 1 login result from %T, got %d", store, len(retrieved)) |
| paddy@46 | 355 } |
| paddy@46 | 356 match, field, expectation, result = compareLogins(login, retrieved[0]) |
| paddy@46 | 357 if !match { |
| paddy@46 | 358 t.Errorf("Expected `%v` in the `%s` field of login retrieved from %T, got `%v`", expectation, field, store, result) |
| paddy@46 | 359 } |
| paddy@116 | 360 err = context.RemoveLogin(login.Value, login.ProfileID) |
| paddy@46 | 361 if err != nil { |
| paddy@46 | 362 t.Errorf("Error removing login from %T: %s", store, err) |
| paddy@46 | 363 } |
| paddy@116 | 364 retrieved, err = context.ListLogins(login.ProfileID, 10, 0) |
| paddy@46 | 365 if len(retrieved) != 0 { |
| paddy@46 | 366 t.Errorf("Expected 0 login results from %T, got %d: %+v", store, len(retrieved), retrieved) |
| paddy@46 | 367 } |
| paddy@116 | 368 err = context.RemoveLogin(login.Value, login.ProfileID) |
| paddy@46 | 369 if err != ErrLoginNotFound { |
| paddy@46 | 370 t.Errorf("Expected ErrLoginNotFound from %T, got %+v", store, err) |
| paddy@46 | 371 } |
| paddy@46 | 372 } |
| paddy@46 | 373 } |
| paddy@47 | 374 |
| paddy@47 | 375 func TestProfileStoreLoginRetrieval(t *testing.T) { |
| paddy@47 | 376 t.Parallel() |
| paddy@47 | 377 profile := Profile{ |
| paddy@47 | 378 ID: uuid.NewID(), |
| paddy@47 | 379 Name: "name", |
| paddy@47 | 380 Passphrase: "passphrase", |
| paddy@47 | 381 Iterations: 10000, |
| paddy@47 | 382 Salt: "salt", |
| paddy@47 | 383 PassphraseScheme: 1, |
| paddy@47 | 384 Compromised: false, |
| paddy@149 | 385 LockedUntil: time.Now().Add(time.Hour).Round(time.Millisecond), |
| paddy@47 | 386 PassphraseReset: "passphrase reset", |
| paddy@149 | 387 PassphraseResetCreated: time.Now().Round(time.Millisecond), |
| paddy@149 | 388 Created: time.Now().Round(time.Millisecond), |
| paddy@149 | 389 LastSeen: time.Now().Round(time.Millisecond), |
| paddy@47 | 390 } |
| paddy@47 | 391 login := Login{ |
| paddy@47 | 392 Type: "type", |
| paddy@47 | 393 Value: "value", |
| paddy@47 | 394 ProfileID: profile.ID, |
| paddy@149 | 395 Created: time.Now().Add(-1 * time.Hour).Round(time.Millisecond), |
| paddy@149 | 396 LastUsed: time.Now().Add(-1 * time.Minute).Round(time.Millisecond), |
| paddy@47 | 397 } |
| paddy@47 | 398 for _, store := range profileStores { |
| paddy@116 | 399 context := Context{profiles: store} |
| paddy@116 | 400 err := context.SaveProfile(profile) |
| paddy@47 | 401 if err != nil { |
| paddy@47 | 402 t.Errorf("Error saving profile in %T: %s", store, err) |
| paddy@47 | 403 } |
| paddy@116 | 404 err = context.AddLogin(login) |
| paddy@47 | 405 if err != nil { |
| paddy@47 | 406 t.Errorf("Error storing login in %T: %s", store, err) |
| paddy@47 | 407 } |
| paddy@116 | 408 retrieved, err := context.GetProfileByLogin(login.Value) |
| paddy@47 | 409 if err != nil { |
| paddy@47 | 410 t.Errorf("Error retrieving profile by login from %T: %s", store, err) |
| paddy@47 | 411 } |
| paddy@47 | 412 match, field, expectation, result := compareProfiles(profile, retrieved) |
| paddy@47 | 413 if !match { |
| paddy@47 | 414 t.Errorf("Expected `%v` in the `%s` field of profile retrieved from %T, got `%v`", expectation, field, store, result) |
| paddy@47 | 415 } |
| paddy@47 | 416 } |
| paddy@47 | 417 } |
| paddy@48 | 418 |
| paddy@48 | 419 func TestProfileChangeValidation(t *testing.T) { |
| paddy@48 | 420 t.Parallel() |
| paddy@48 | 421 passphraseScheme := 1 |
| paddy@48 | 422 passphraseReset := "reset" |
| paddy@48 | 423 salt := "salt" |
| paddy@69 | 424 iterations := 100 |
| paddy@48 | 425 emptyName := "" |
| paddy@48 | 426 enteredName := "Paddy" |
| paddy@48 | 427 okPassphrase := "this is a decent passphrase" |
| paddy@48 | 428 compromised := true |
| paddy@149 | 429 lockedUntil := time.Now().Round(time.Millisecond) |
| paddy@149 | 430 resetCreated := time.Now().Round(time.Millisecond) |
| paddy@149 | 431 lastSeen := time.Now().Round(time.Millisecond) |
| paddy@48 | 432 changes := map[*ProfileChange]error{ |
| paddy@48 | 433 &ProfileChange{}: ErrEmptyChange, |
| paddy@48 | 434 &ProfileChange{PassphraseScheme: &passphraseScheme}: ErrMissingPassphrase, |
| paddy@48 | 435 &ProfileChange{PassphraseScheme: &passphraseScheme, Passphrase: &okPassphrase}: nil, |
| paddy@48 | 436 &ProfileChange{PassphraseReset: &passphraseReset}: ErrMissingPassphraseResetCreated, |
| paddy@48 | 437 &ProfileChange{PassphraseReset: &passphraseReset, PassphraseResetCreated: &resetCreated}: nil, |
| paddy@48 | 438 &ProfileChange{Salt: &salt}: ErrMissingPassphrase, |
| paddy@48 | 439 &ProfileChange{Salt: &salt, Passphrase: &okPassphrase}: nil, |
| paddy@48 | 440 &ProfileChange{Iterations: &iterations}: ErrMissingPassphrase, |
| paddy@48 | 441 &ProfileChange{Iterations: &iterations, Passphrase: &okPassphrase}: nil, |
| paddy@48 | 442 &ProfileChange{Passphrase: &okPassphrase}: nil, |
| paddy@48 | 443 &ProfileChange{Name: &emptyName}: nil, |
| paddy@48 | 444 &ProfileChange{Name: &enteredName}: nil, |
| paddy@48 | 445 &ProfileChange{Compromised: &compromised}: nil, |
| paddy@48 | 446 &ProfileChange{LockedUntil: &lockedUntil}: nil, |
| paddy@48 | 447 &ProfileChange{LastSeen: &lastSeen}: nil, |
| paddy@48 | 448 &ProfileChange{PassphraseResetCreated: &resetCreated}: ErrMissingPassphraseReset, |
| paddy@48 | 449 } |
| paddy@48 | 450 for change, expectedErr := range changes { |
| paddy@48 | 451 if err := change.Validate(); err != expectedErr { |
| paddy@48 | 452 t.Errorf("Expected %+v to give an error of %v, gave %v", change, expectedErr, err) |
| paddy@48 | 453 } |
| paddy@48 | 454 } |
| paddy@48 | 455 } |
| paddy@48 | 456 |
| paddy@48 | 457 func TestBulkProfileChangeValidation(t *testing.T) { |
| paddy@48 | 458 t.Parallel() |
| paddy@48 | 459 compromised := true |
| paddy@48 | 460 changes := map[*BulkProfileChange]error{ |
| paddy@48 | 461 &BulkProfileChange{}: ErrEmptyChange, |
| paddy@48 | 462 &BulkProfileChange{Compromised: &compromised}: nil, |
| paddy@48 | 463 } |
| paddy@48 | 464 for change, expectedErr := range changes { |
| paddy@48 | 465 if err := change.Validate(); err != expectedErr { |
| paddy@48 | 466 t.Errorf("Expected %+v to give an error of %v, gave %v", change, expectedErr, err) |
| paddy@48 | 467 } |
| paddy@48 | 468 } |
| paddy@48 | 469 } |
| paddy@128 | 470 |
| paddy@128 | 471 // BUG(paddy): We need to test the validateNewProfileRequest helper. |
| paddy@128 | 472 // BUG(paddy): We need to test the CreateProfileHandler. |
| paddy@148 | 473 // BUG(paddy): We need to test that deleting works as we expect. |