auth

Paddy 2015-03-21 Parent:b5432f50f057 Child:8267e1c8bcd1

148:06fb735031bb Go to Latest

auth/profile_test.go

Do a first, naive pass at storing profiles in Postgres. This is untested against an actual database. It's a best-guess attempt at SQL. It _should_ work. I think. Start storing things in Postgres, starting with Profiles and Logins. This necessitates the addition of a Deleted property to the Profile type, because I'm not deleting those in case of accidental deletion. Logins, though, we'll delete. This also necessitates updating the profileStore interface to no longer have a deleteProfile method, because we're tracking that through updates now. Then we need to update our profileStore tests, because they no longer clean up after themselves. Which, come to think of it, may cause some problems later.

History
paddy@38 1 package auth
paddy@38 2
paddy@38 3 import (
paddy@38 4 "fmt"
paddy@38 5 "testing"
paddy@38 6 "time"
paddy@38 7
paddy@107 8 "code.secondbit.org/uuid.hg"
paddy@38 9 )
paddy@38 10
paddy@38 11 const (
paddy@38 12 profileChangeName = 1 << iota
paddy@38 13 profileChangePassphrase
paddy@38 14 profileChangeIterations
paddy@38 15 profileChangeSalt
paddy@38 16 profileChangePassphraseScheme
paddy@38 17 profileChangeCompromised
paddy@38 18 profileChangeLockedUntil
paddy@38 19 profileChangePassphraseReset
paddy@38 20 profileChangePassphraseResetCreated
paddy@38 21 profileChangeLastSeen
paddy@38 22 )
paddy@38 23
paddy@57 24 var profileStores = []profileStore{NewMemstore()}
paddy@38 25
paddy@38 26 func compareProfiles(profile1, profile2 Profile) (success bool, field string, val1, val2 interface{}) {
paddy@38 27 if !profile1.ID.Equal(profile2.ID) {
paddy@38 28 return false, "ID", profile1.ID, profile2.ID
paddy@38 29 }
paddy@38 30 if profile1.Name != profile2.Name {
paddy@38 31 return false, "name", profile1.Name, profile2.Name
paddy@38 32 }
paddy@38 33 if profile1.Passphrase != profile2.Passphrase {
paddy@38 34 return false, "passphrase", profile1.Passphrase, profile2.Passphrase
paddy@38 35 }
paddy@38 36 if profile1.Iterations != profile2.Iterations {
paddy@38 37 return false, "iterations", profile1.Iterations, profile2.Iterations
paddy@38 38 }
paddy@38 39 if profile1.Salt != profile2.Salt {
paddy@38 40 return false, "salt", profile1.Salt, profile2.Salt
paddy@38 41 }
paddy@38 42 if profile1.PassphraseScheme != profile2.PassphraseScheme {
paddy@38 43 return false, "passphrase scheme", profile1.PassphraseScheme, profile2.PassphraseScheme
paddy@38 44 }
paddy@38 45 if profile1.Compromised != profile2.Compromised {
paddy@38 46 return false, "compromised", profile1.Compromised, profile2.Compromised
paddy@38 47 }
paddy@38 48 if !profile1.LockedUntil.Equal(profile2.LockedUntil) {
paddy@38 49 return false, "locked until", profile1.LockedUntil, profile2.LockedUntil
paddy@38 50 }
paddy@38 51 if profile1.PassphraseReset != profile2.PassphraseReset {
paddy@38 52 return false, "passphrase reset", profile1.PassphraseReset, profile2.PassphraseReset
paddy@38 53 }
paddy@38 54 if !profile1.PassphraseResetCreated.Equal(profile2.PassphraseResetCreated) {
paddy@38 55 return false, "passphrase reset created", profile1.PassphraseResetCreated, profile2.PassphraseResetCreated
paddy@38 56 }
paddy@38 57 if !profile1.Created.Equal(profile2.Created) {
paddy@38 58 return false, "created", profile1.Created, profile2.Created
paddy@38 59 }
paddy@38 60 if !profile1.LastSeen.Equal(profile2.LastSeen) {
paddy@38 61 return false, "last seen", profile1.LastSeen, profile2.LastSeen
paddy@38 62 }
paddy@148 63 if profile1.Deleted != profile2.Deleted {
paddy@148 64 return false, "deleted", profile1.Deleted, profile2.Deleted
paddy@148 65 }
paddy@38 66 return true, "", nil, nil
paddy@38 67 }
paddy@38 68
paddy@46 69 func compareLogins(login1, login2 Login) (success bool, field string, val1, val2 interface{}) {
paddy@46 70 if login1.Type != login2.Type {
paddy@46 71 return false, "Type", login1.Type, login2.Type
paddy@46 72 }
paddy@46 73 if login1.Value != login2.Value {
paddy@46 74 return false, "Value", login1.Value, login2.Value
paddy@46 75 }
paddy@46 76 if !login1.ProfileID.Equal(login2.ProfileID) {
paddy@46 77 return false, "ProfileID", login1.ProfileID, login2.ProfileID
paddy@46 78 }
paddy@46 79 if !login1.Created.Equal(login2.Created) {
paddy@46 80 return false, "Created", login1.Created, login2.Created
paddy@46 81 }
paddy@46 82 if !login1.LastUsed.Equal(login2.LastUsed) {
paddy@46 83 return false, "LastUsed", login1.LastUsed, login2.LastUsed
paddy@46 84 }
paddy@46 85 return true, "", nil, nil
paddy@46 86 }
paddy@46 87
paddy@38 88 func TestProfileStoreSuccess(t *testing.T) {
paddy@38 89 t.Parallel()
paddy@38 90 profile := Profile{
paddy@38 91 ID: uuid.NewID(),
paddy@38 92 Name: "name",
paddy@38 93 Passphrase: "passphrase",
paddy@38 94 Iterations: 10000,
paddy@38 95 Salt: "salt",
paddy@38 96 PassphraseScheme: 1,
paddy@38 97 Compromised: false,
paddy@38 98 LockedUntil: time.Now().Add(time.Hour),
paddy@38 99 PassphraseReset: "passphrase reset",
paddy@38 100 PassphraseResetCreated: time.Now(),
paddy@38 101 Created: time.Now(),
paddy@38 102 LastSeen: time.Now(),
paddy@38 103 }
paddy@38 104 for _, store := range profileStores {
paddy@116 105 context := Context{profiles: store}
paddy@116 106 err := context.SaveProfile(profile)
paddy@38 107 if err != nil {
paddy@38 108 t.Errorf("Error saving profile to %T: %s", store, err)
paddy@38 109 }
paddy@116 110 err = context.SaveProfile(profile)
paddy@38 111 if err != ErrProfileAlreadyExists {
paddy@38 112 t.Errorf("Expected ErrProfileAlreadyExists from %T, got %+v", store, err)
paddy@38 113 }
paddy@116 114 retrieved, err := context.GetProfileByID(profile.ID)
paddy@38 115 if err != nil {
paddy@38 116 t.Errorf("Error retrieving profile from %T: %s", store, err)
paddy@38 117 }
paddy@38 118 match, field, expectation, result := compareProfiles(profile, retrieved)
paddy@38 119 if !match {
paddy@38 120 t.Errorf("Expected `%v` in the `%s` field of profile retrieved from %T, got `%v`", expectation, field, store, result)
paddy@38 121 }
paddy@148 122 deleted := true
paddy@148 123 err = context.UpdateProfile(profile.ID, ProfileChange{Deleted: &deleted})
paddy@38 124 if err != nil {
paddy@38 125 t.Errorf("Error removing profile from %T: %s", store, err)
paddy@38 126 }
paddy@116 127 retrieved, err = context.GetProfileByID(profile.ID)
paddy@38 128 if err != ErrProfileNotFound {
paddy@38 129 t.Errorf("Expected ErrProfileNotFound from %T, got %+v and %+v", store, retrieved, err)
paddy@38 130 }
paddy@38 131 }
paddy@38 132 }
paddy@38 133
paddy@38 134 func TestProfileUpdates(t *testing.T) {
paddy@38 135 t.Parallel()
paddy@38 136 variations := 1 << 10
paddy@38 137 profile := Profile{
paddy@38 138 ID: uuid.NewID(),
paddy@38 139 Name: "name",
paddy@38 140 Passphrase: "passphrase",
paddy@38 141 Iterations: 10000,
paddy@38 142 Salt: "salt",
paddy@38 143 PassphraseScheme: 1,
paddy@38 144 Compromised: false,
paddy@38 145 LockedUntil: time.Now().Add(time.Hour),
paddy@38 146 PassphraseReset: "passphrase reset",
paddy@38 147 PassphraseResetCreated: time.Now(),
paddy@38 148 Created: time.Now(),
paddy@38 149 LastSeen: time.Now(),
paddy@38 150 }
paddy@38 151 for i := 0; i < variations; i++ {
paddy@38 152 var name, passphrase, salt, passphraseReset string
paddy@69 153 var iterations int
paddy@38 154 var lockedUntil, passphraseResetCreated, lastSeen time.Time
paddy@38 155 var passphraseScheme int
paddy@38 156 var compromised bool
paddy@38 157
paddy@148 158 profile.ID = uuid.NewID()
paddy@38 159 change := ProfileChange{}
paddy@38 160 expectation := profile
paddy@38 161 result := profile
paddy@38 162 if i&profileChangeName != 0 {
paddy@38 163 name = fmt.Sprintf("name-%d", i)
paddy@38 164 change.Name = &name
paddy@38 165 expectation.Name = name
paddy@38 166 }
paddy@38 167 if i&profileChangePassphrase != 0 {
paddy@38 168 passphrase = fmt.Sprintf("passphrase-%d", i)
paddy@38 169 change.Passphrase = &passphrase
paddy@38 170 expectation.Passphrase = passphrase
paddy@38 171 }
paddy@38 172 if i&profileChangeIterations != 0 {
paddy@69 173 iterations = i
paddy@38 174 change.Iterations = &iterations
paddy@38 175 expectation.Iterations = iterations
paddy@38 176 }
paddy@38 177 if i&profileChangeSalt != 0 {
paddy@38 178 salt = fmt.Sprintf("salt-%d", i)
paddy@38 179 change.Salt = &salt
paddy@38 180 expectation.Salt = salt
paddy@38 181 }
paddy@38 182 if i&profileChangePassphraseScheme != 0 {
paddy@38 183 passphraseScheme = i
paddy@38 184 change.PassphraseScheme = &passphraseScheme
paddy@38 185 expectation.PassphraseScheme = passphraseScheme
paddy@38 186 }
paddy@38 187 if i&profileChangeCompromised != 0 {
paddy@38 188 compromised = i%2 != 0
paddy@38 189 change.Compromised = &compromised
paddy@38 190 expectation.Compromised = compromised
paddy@38 191 }
paddy@38 192 if i&profileChangeLockedUntil != 0 {
paddy@38 193 lockedUntil = time.Now()
paddy@38 194 change.LockedUntil = &lockedUntil
paddy@38 195 expectation.LockedUntil = lockedUntil
paddy@38 196 }
paddy@38 197 if i&profileChangePassphraseReset != 0 {
paddy@38 198 passphraseReset = fmt.Sprintf("passphraseReset-%d", i)
paddy@38 199 change.PassphraseReset = &passphraseReset
paddy@38 200 expectation.PassphraseReset = passphraseReset
paddy@38 201 }
paddy@38 202 if i&profileChangePassphraseResetCreated != 0 {
paddy@38 203 passphraseResetCreated = time.Now()
paddy@38 204 change.PassphraseResetCreated = &passphraseResetCreated
paddy@38 205 expectation.PassphraseResetCreated = passphraseResetCreated
paddy@38 206 }
paddy@38 207 if i&profileChangeLastSeen != 0 {
paddy@38 208 lastSeen = time.Now()
paddy@38 209 change.LastSeen = &lastSeen
paddy@38 210 expectation.LastSeen = lastSeen
paddy@38 211 }
paddy@38 212 result.ApplyChange(change)
paddy@38 213 match, field, expected, got := compareProfiles(expectation, result)
paddy@38 214 if !match {
paddy@38 215 t.Errorf("Expected field `%s` to be `%v`, got `%v`", field, expected, got)
paddy@38 216 }
paddy@38 217 for _, store := range profileStores {
paddy@116 218 context := Context{profiles: store}
paddy@116 219 err := context.SaveProfile(profile)
paddy@38 220 if err != nil {
paddy@38 221 t.Errorf("Error saving profile in %T: %s", store, err)
paddy@38 222 }
paddy@116 223 err = context.UpdateProfile(profile.ID, change)
paddy@38 224 if err != nil {
paddy@38 225 t.Errorf("Error updating profile in %T: %s", store, err)
paddy@38 226 }
paddy@116 227 retrieved, err := context.GetProfileByID(profile.ID)
paddy@38 228 if err != nil {
paddy@38 229 t.Errorf("Error getting profile from %T: %s", store, err)
paddy@38 230 }
paddy@38 231 match, field, expected, got = compareProfiles(expectation, retrieved)
paddy@38 232 if !match {
paddy@38 233 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
paddy@38 234 }
paddy@38 235 }
paddy@38 236 }
paddy@38 237 }
paddy@45 238
paddy@45 239 func TestProfilesUpdates(t *testing.T) {
paddy@45 240 profile1 := Profile{
paddy@45 241 ID: uuid.NewID(),
paddy@45 242 }
paddy@45 243 profile2 := Profile{
paddy@45 244 ID: uuid.NewID(),
paddy@45 245 }
paddy@45 246 profile3 := Profile{
paddy@45 247 ID: uuid.NewID(),
paddy@45 248 }
paddy@45 249 truth := true
paddy@45 250 change := BulkProfileChange{
paddy@45 251 Compromised: &truth,
paddy@45 252 }
paddy@45 253 for _, store := range profileStores {
paddy@116 254 context := Context{profiles: store}
paddy@116 255 err := context.SaveProfile(profile1)
paddy@45 256 if err != nil {
paddy@45 257 t.Errorf("Error saving profile in %T: %s", store, err)
paddy@45 258 }
paddy@116 259 err = context.SaveProfile(profile2)
paddy@45 260 if err != nil {
paddy@45 261 t.Errorf("Error saving profile in %T: %s", store, err)
paddy@45 262 }
paddy@116 263 err = context.SaveProfile(profile3)
paddy@45 264 if err != nil {
paddy@45 265 t.Errorf("Error saving profile in %T: %s", store, err)
paddy@45 266 }
paddy@116 267 err = context.UpdateProfiles([]uuid.ID{profile1.ID, profile3.ID}, change)
paddy@45 268 if err != nil {
paddy@45 269 t.Errorf("Error updating profile in %T: %s", store, err)
paddy@45 270 }
paddy@45 271 profile1.Compromised = truth
paddy@45 272 profile3.Compromised = truth
paddy@116 273 retrieved, err := context.GetProfileByID(profile1.ID)
paddy@45 274 if err != nil {
paddy@45 275 t.Errorf("Error getting profile from %T: %s", store, err)
paddy@45 276 }
paddy@45 277 match, field, expected, got := compareProfiles(profile1, retrieved)
paddy@45 278 if !match {
paddy@45 279 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
paddy@45 280 }
paddy@116 281 retrieved, err = context.GetProfileByID(profile2.ID)
paddy@45 282 if err != nil {
paddy@45 283 t.Errorf("Error getting profile from %T: %s", store, err)
paddy@45 284 }
paddy@45 285 match, field, expected, got = compareProfiles(profile2, retrieved)
paddy@45 286 if !match {
paddy@45 287 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
paddy@45 288 }
paddy@116 289 retrieved, err = context.GetProfileByID(profile3.ID)
paddy@45 290 if err != nil {
paddy@45 291 t.Errorf("Error getting profile from %T: %s", store, err)
paddy@45 292 }
paddy@45 293 match, field, expected, got = compareProfiles(profile3, retrieved)
paddy@45 294 if !match {
paddy@45 295 t.Errorf("Expected field `%s` to be `%v`, got `%v` from %T", field, expected, got, store)
paddy@45 296 }
paddy@45 297 }
paddy@45 298 }
paddy@46 299
paddy@46 300 func TestProfileStoreLoginSuccess(t *testing.T) {
paddy@46 301 t.Parallel()
paddy@46 302 login := Login{
paddy@46 303 Type: "type",
paddy@46 304 Value: "value",
paddy@46 305 ProfileID: uuid.NewID(),
paddy@46 306 Created: time.Now().Add(-1 * time.Hour),
paddy@46 307 LastUsed: time.Now().Add(-1 * time.Minute),
paddy@46 308 }
paddy@46 309 for _, store := range profileStores {
paddy@116 310 context := Context{profiles: store}
paddy@116 311 err := context.AddLogin(login)
paddy@46 312 if err != nil {
paddy@46 313 t.Errorf("Error adding login to %T: %s", store, err)
paddy@46 314 }
paddy@116 315 err = context.AddLogin(login)
paddy@46 316 if err != ErrLoginAlreadyExists {
paddy@46 317 t.Errorf("Expected ErrLoginAlreadyExists from %T, got %+v", store, err)
paddy@46 318 }
paddy@116 319 retrieved, err := context.ListLogins(login.ProfileID, 10, 0)
paddy@46 320 if err != nil {
paddy@46 321 t.Errorf("Error retrieving logins from %T: %s", store, err)
paddy@46 322 }
paddy@46 323 if len(retrieved) != 1 {
paddy@46 324 t.Errorf("Expected 1 login result from %T, got %d", store, len(retrieved))
paddy@46 325 }
paddy@46 326 match, field, expectation, result := compareLogins(login, retrieved[0])
paddy@46 327 if !match {
paddy@46 328 t.Errorf("Expected `%v` in the `%s` field of login retrieved from %T, got `%v`", expectation, field, store, result)
paddy@46 329 }
paddy@46 330 lastUsed := time.Now()
paddy@116 331 err = context.RecordLoginUse(login.Value, lastUsed)
paddy@46 332 if err != nil {
paddy@46 333 t.Errorf("Error recording use of login to %T: %s", store, err)
paddy@46 334 }
paddy@46 335 login.LastUsed = lastUsed
paddy@116 336 retrieved, err = context.ListLogins(login.ProfileID, 10, 0)
paddy@46 337 if err != nil {
paddy@46 338 t.Errorf("Error retrieving logins from %T: %s", store, err)
paddy@46 339 }
paddy@46 340 if len(retrieved) != 1 {
paddy@46 341 t.Errorf("Expected 1 login result from %T, got %d", store, len(retrieved))
paddy@46 342 }
paddy@46 343 match, field, expectation, result = compareLogins(login, retrieved[0])
paddy@46 344 if !match {
paddy@46 345 t.Errorf("Expected `%v` in the `%s` field of login retrieved from %T, got `%v`", expectation, field, store, result)
paddy@46 346 }
paddy@116 347 err = context.RemoveLogin(login.Value, login.ProfileID)
paddy@46 348 if err != nil {
paddy@46 349 t.Errorf("Error removing login from %T: %s", store, err)
paddy@46 350 }
paddy@116 351 retrieved, err = context.ListLogins(login.ProfileID, 10, 0)
paddy@46 352 if len(retrieved) != 0 {
paddy@46 353 t.Errorf("Expected 0 login results from %T, got %d: %+v", store, len(retrieved), retrieved)
paddy@46 354 }
paddy@116 355 err = context.RemoveLogin(login.Value, login.ProfileID)
paddy@46 356 if err != ErrLoginNotFound {
paddy@46 357 t.Errorf("Expected ErrLoginNotFound from %T, got %+v", store, err)
paddy@46 358 }
paddy@46 359 }
paddy@46 360 }
paddy@47 361
paddy@47 362 func TestProfileStoreLoginRetrieval(t *testing.T) {
paddy@47 363 t.Parallel()
paddy@47 364 profile := Profile{
paddy@47 365 ID: uuid.NewID(),
paddy@47 366 Name: "name",
paddy@47 367 Passphrase: "passphrase",
paddy@47 368 Iterations: 10000,
paddy@47 369 Salt: "salt",
paddy@47 370 PassphraseScheme: 1,
paddy@47 371 Compromised: false,
paddy@47 372 LockedUntil: time.Now().Add(time.Hour),
paddy@47 373 PassphraseReset: "passphrase reset",
paddy@47 374 PassphraseResetCreated: time.Now(),
paddy@47 375 Created: time.Now(),
paddy@47 376 LastSeen: time.Now(),
paddy@47 377 }
paddy@47 378 login := Login{
paddy@47 379 Type: "type",
paddy@47 380 Value: "value",
paddy@47 381 ProfileID: profile.ID,
paddy@47 382 Created: time.Now().Add(-1 * time.Hour),
paddy@47 383 LastUsed: time.Now().Add(-1 * time.Minute),
paddy@47 384 }
paddy@47 385 for _, store := range profileStores {
paddy@116 386 context := Context{profiles: store}
paddy@116 387 err := context.SaveProfile(profile)
paddy@47 388 if err != nil {
paddy@47 389 t.Errorf("Error saving profile in %T: %s", store, err)
paddy@47 390 }
paddy@116 391 err = context.AddLogin(login)
paddy@47 392 if err != nil {
paddy@47 393 t.Errorf("Error storing login in %T: %s", store, err)
paddy@47 394 }
paddy@116 395 retrieved, err := context.GetProfileByLogin(login.Value)
paddy@47 396 if err != nil {
paddy@47 397 t.Errorf("Error retrieving profile by login from %T: %s", store, err)
paddy@47 398 }
paddy@47 399 match, field, expectation, result := compareProfiles(profile, retrieved)
paddy@47 400 if !match {
paddy@47 401 t.Errorf("Expected `%v` in the `%s` field of profile retrieved from %T, got `%v`", expectation, field, store, result)
paddy@47 402 }
paddy@47 403 }
paddy@47 404 }
paddy@48 405
paddy@48 406 func TestProfileChangeValidation(t *testing.T) {
paddy@48 407 t.Parallel()
paddy@48 408 passphraseScheme := 1
paddy@48 409 passphraseReset := "reset"
paddy@48 410 salt := "salt"
paddy@69 411 iterations := 100
paddy@48 412 emptyName := ""
paddy@48 413 enteredName := "Paddy"
paddy@48 414 okPassphrase := "this is a decent passphrase"
paddy@48 415 compromised := true
paddy@48 416 lockedUntil := time.Now()
paddy@48 417 resetCreated := time.Now()
paddy@48 418 lastSeen := time.Now()
paddy@48 419 changes := map[*ProfileChange]error{
paddy@48 420 &ProfileChange{}: ErrEmptyChange,
paddy@48 421 &ProfileChange{PassphraseScheme: &passphraseScheme}: ErrMissingPassphrase,
paddy@48 422 &ProfileChange{PassphraseScheme: &passphraseScheme, Passphrase: &okPassphrase}: nil,
paddy@48 423 &ProfileChange{PassphraseReset: &passphraseReset}: ErrMissingPassphraseResetCreated,
paddy@48 424 &ProfileChange{PassphraseReset: &passphraseReset, PassphraseResetCreated: &resetCreated}: nil,
paddy@48 425 &ProfileChange{Salt: &salt}: ErrMissingPassphrase,
paddy@48 426 &ProfileChange{Salt: &salt, Passphrase: &okPassphrase}: nil,
paddy@48 427 &ProfileChange{Iterations: &iterations}: ErrMissingPassphrase,
paddy@48 428 &ProfileChange{Iterations: &iterations, Passphrase: &okPassphrase}: nil,
paddy@48 429 &ProfileChange{Passphrase: &okPassphrase}: nil,
paddy@48 430 &ProfileChange{Name: &emptyName}: nil,
paddy@48 431 &ProfileChange{Name: &enteredName}: nil,
paddy@48 432 &ProfileChange{Compromised: &compromised}: nil,
paddy@48 433 &ProfileChange{LockedUntil: &lockedUntil}: nil,
paddy@48 434 &ProfileChange{LastSeen: &lastSeen}: nil,
paddy@48 435 &ProfileChange{PassphraseResetCreated: &resetCreated}: ErrMissingPassphraseReset,
paddy@48 436 }
paddy@48 437 for change, expectedErr := range changes {
paddy@48 438 if err := change.Validate(); err != expectedErr {
paddy@48 439 t.Errorf("Expected %+v to give an error of %v, gave %v", change, expectedErr, err)
paddy@48 440 }
paddy@48 441 }
paddy@48 442 }
paddy@48 443
paddy@48 444 func TestBulkProfileChangeValidation(t *testing.T) {
paddy@48 445 t.Parallel()
paddy@48 446 compromised := true
paddy@48 447 changes := map[*BulkProfileChange]error{
paddy@48 448 &BulkProfileChange{}: ErrEmptyChange,
paddy@48 449 &BulkProfileChange{Compromised: &compromised}: nil,
paddy@48 450 }
paddy@48 451 for change, expectedErr := range changes {
paddy@48 452 if err := change.Validate(); err != expectedErr {
paddy@48 453 t.Errorf("Expected %+v to give an error of %v, gave %v", change, expectedErr, err)
paddy@48 454 }
paddy@48 455 }
paddy@48 456 }
paddy@128 457
paddy@128 458 // BUG(paddy): We need to test the validateNewProfileRequest helper.
paddy@128 459 // BUG(paddy): We need to test the CreateProfileHandler.
paddy@148 460 // BUG(paddy): We need to test that deleting works as we expect.