// Ensure that an account can retrieve all associated users. func TestAccountUsers(t *testing.T) { withDB(func(db *DB) { // Create two accounts. db.Do(func(tx *Tx) error { a1 := &Account{} assert.NoError(t, tx.CreateAccount(a1)) a2 := &Account{} assert.NoError(t, tx.CreateAccount(a2)) return nil }) // Add users to first account. db.Do(func(tx *Tx) error { a1, _ := tx.Account(1) assert.NoError(t, a1.CreateUser(&User{Email: "*****@*****.**", Password: "******"})) assert.NoError(t, a1.CreateUser(&User{Email: "*****@*****.**", Password: "******"})) return nil }) // Add users to second account. db.Do(func(tx *Tx) error { a2, _ := tx.Account(2) assert.NoError(t, a2.CreateUser(&User{Email: "*****@*****.**", Password: "******"})) return nil }) // Check first account users. db.With(func(tx *Tx) error { a1, _ := tx.Account(1) users, err := a1.Users() if assert.NoError(t, err) && assert.Equal(t, len(users), 2) { assert.Equal(t, users[0].Tx, tx) assert.Equal(t, users[0].ID(), 2) assert.Equal(t, users[0].AccountID, 1) assert.Equal(t, users[0].Email, "*****@*****.**") assert.Equal(t, users[1].Tx, tx) assert.Equal(t, users[1].ID(), 1) assert.Equal(t, users[1].AccountID, 1) assert.Equal(t, users[1].Email, "*****@*****.**") } return nil }) // Check second account users. db.With(func(tx *Tx) error { a2, _ := tx.Account(2) users, err := a2.Users() if assert.NoError(t, err) && assert.Equal(t, len(users), 1) { assert.Equal(t, users[0].Tx, tx) assert.Equal(t, users[0].ID(), 3) assert.Equal(t, users[0].AccountID, 2) assert.Equal(t, users[0].Email, "*****@*****.**") } return nil }) }) }
// Ensure that an account can generate a random API key. func TestAccountGenerateAPIKey(t *testing.T) { withDB(func(db *DB) { db.Do(func(tx *Tx) error { a := &Account{} assert.NoError(t, tx.CreateAccount(a)) // Check for an API key. assert.Equal(t, len(a.APIKey), 36) // Lookup account by API key. a2, err := tx.AccountByAPIKey(a.APIKey) assert.NoError(t, err) assert.Equal(t, a2.ID(), 1) // Regenerate key. apiKey := a.APIKey assert.NoError(t, a.GenerateAPIKey()) // Make sure it's not the same as before. assert.NotEqual(t, a.APIKey, apiKey) // Make sure we can lookup by the new key and not the old. a3, err := tx.AccountByAPIKey(a.APIKey) assert.NoError(t, err) assert.Equal(t, a3.ID(), 1) a4, err := tx.AccountByAPIKey(apiKey) assert.Equal(t, err, ErrAccountNotFound) assert.Nil(t, a4) return nil }) }) }
// Ensure that the database can retrieve a user by email. func TestDBUserByEmail(t *testing.T) { withDB(func(db *DB) { db.Do(func(tx *Tx) error { // Add account and users. a := &Account{} assert.NoError(t, tx.CreateAccount(a)) assert.NoError(t, a.CreateUser(&User{Email: "*****@*****.**", Password: "******"})) assert.NoError(t, a.CreateUser(&User{Email: "*****@*****.**", Password: "******"})) // Find user. u, _ := tx.UserByEmail("*****@*****.**") assert.Equal(t, u.ID(), 2) // Delete user and find. assert.NoError(t, u.Delete()) _, err := tx.UserByEmail("*****@*****.**") assert.Equal(t, err, ErrUserNotFound) // Re-add and find again. assert.NoError(t, a.CreateUser(&User{Email: "*****@*****.**", Password: "******"})) u, _ = tx.UserByEmail("*****@*****.**") assert.Equal(t, u.ID(), 3) return nil }) }) }
// Ensure that an account can retrieve a list of unique resources. func TestAccountResources(t *testing.T) { withDB(func(db *DB) { db.Do(func(tx *Tx) error { a1, a2, a3 := &Account{}, &Account{}, &Account{} tx.CreateAccount(a1) tx.CreateAccount(a2) tx.CreateAccount(a3) a1.Reset() a2.Reset() a3.Reset() // Add some events. assert.NoError(t, a1.Track(newTestEvent("2000-01-01T00:00:00Z", "john", "DEV0", "web", "/", "view", nil))) assert.NoError(t, a1.Track(newTestEvent("2000-01-01T00:00:01Z", "john", "DEV1", "web", "/signup", "view", nil))) assert.NoError(t, a1.Track(newTestEvent("2000-01-01T00:00:02Z", "susy", "DEV2", "web", "/cancel", "view", nil))) assert.NoError(t, a2.Track(newTestEvent("2000-01-01T00:00:02Z", "john", "DEV2", "web", "/blah", "view", nil))) // Retrieve resources. resources, err := a1.Resources() assert.NoError(t, err) assert.Equal(t, resources, []string{"/", "/cancel", "/signup"}) resources, err = a2.Resources() assert.NoError(t, err) assert.Equal(t, resources, []string{"/blah"}) resources, err = a3.Resources() assert.NoError(t, err) assert.Equal(t, resources, []string{}) return nil }) }) }
// Ensure that an account can retrieve all associated funnels. func TestAccountFunnels(t *testing.T) { withDB(func(db *DB) { db.Do(func(tx *Tx) error { a1 := &Account{} a2 := &Account{} assert.NoError(t, tx.CreateAccount(a1)) assert.NoError(t, tx.CreateAccount(a2)) // Add funnels to first account. assert.NoError(t, a1.CreateFunnel(&Funnel{Name: "Funnel B", Steps: []*FunnelStep{{Condition: "action == 'foo'"}}})) assert.NoError(t, a1.CreateFunnel(&Funnel{Name: "Funnel A", Steps: []*FunnelStep{{Condition: "action == 'foo'"}}})) // Add funnels to second account. assert.NoError(t, a2.CreateFunnel(&Funnel{Name: "Funnel C", Steps: []*FunnelStep{{Condition: "action == 'foo'"}}})) return nil }) // Check first account. db.With(func(tx *Tx) error { a, _ := tx.Account(1) funnels, err := a.Funnels() if assert.NoError(t, err) && assert.Equal(t, len(funnels), 2) { assert.Equal(t, funnels[0].Tx, tx) assert.Equal(t, funnels[0].ID(), 2) assert.Equal(t, funnels[0].AccountID, 1) assert.Equal(t, funnels[0].Name, "Funnel A") assert.Equal(t, funnels[1].Tx, tx) assert.Equal(t, funnels[1].ID(), 1) assert.Equal(t, funnels[1].AccountID, 1) assert.Equal(t, funnels[1].Name, "Funnel B") } // Make sure we can only get a1 funnels. f, err := a.Funnel(1) assert.NoError(t, err) assert.NotNil(t, f) f, err = a.Funnel(3) assert.Equal(t, err, ErrFunnelNotFound) assert.Nil(t, f) return nil }) // Check second account's funnels. db.With(func(tx *Tx) error { a, _ := tx.Account(2) funnels, err := a.Funnels() if assert.NoError(t, err) && assert.Equal(t, len(funnels), 1) { assert.Equal(t, funnels[0].Tx, tx) assert.Equal(t, funnels[0].ID(), 3) assert.Equal(t, funnels[0].AccountID, 2) assert.Equal(t, funnels[0].Name, "Funnel C") } return nil }) }) }
// Ensure that retrieving a missing account returns an error. func TestDBAccountNotFound(t *testing.T) { withDB(func(db *DB) { db.Do(func(tx *Tx) error { a, err := tx.Account(1) assert.Equal(t, err, ErrAccountNotFound) assert.Nil(t, a) return nil }) }) }
// Ensure that creating a funnel without a name returns an error. func TestFunnelCreateMissingName(t *testing.T) { withDB(func(db *DB) { db.Do(func(tx *Tx) error { a := &Account{} assert.NoError(t, tx.CreateAccount(a)) assert.Equal(t, a.CreateFunnel(&Funnel{Steps: []*FunnelStep{{Condition: "action == 'foo'"}}}), ErrFunnelNameRequired) return nil }) }) }
// Ensure that retrieving a missing user returns an error. func TestDBUserNotFound(t *testing.T) { withDB(func(db *DB) { db.Do(func(tx *Tx) error { u, err := tx.User(1) assert.Equal(t, err, ErrUserNotFound) assert.Nil(t, u) return nil }) }) }
// Ensure that creating a funnel without steps returns an error. func TestFunnelCreateMissingSteps(t *testing.T) { withDB(func(db *DB) { db.Do(func(tx *Tx) error { a := &Account{} assert.NoError(t, tx.CreateAccount(a)) assert.Equal(t, a.CreateFunnel(&Funnel{Name: "Funnel Y"}), ErrFunnelStepsRequired) return nil }) }) }
// Ensure that an account can be deleted. func TestAccountDelete(t *testing.T) { withDB(func(db *DB) { // Create account. err := db.Do(func(tx *Tx) error { return tx.CreateAccount(&Account{}) }) assert.NoError(t, err) // Retrieve and delete account. err = db.Do(func(tx *Tx) error { a, _ := tx.Account(1) return a.Delete() }) assert.NoError(t, err) // Retrieve the account again. err = db.With(func(tx *Tx) error { _, err := tx.Account(1) return err }) assert.Equal(t, err, ErrAccountNotFound) }) }
// Ensure that a funnel query can be executed. func TestFunnelQuery(t *testing.T) { withDB(func(db *DB) { db.Do(func(tx *Tx) error { a := &Account{} f := &Funnel{ Name: "FUN", Steps: []*FunnelStep{ {Condition: "resource == '/home'"}, {Condition: "resource == '/signup'"}, {Condition: "resource == '/checkout'"}, }, } assert.NoError(t, tx.CreateAccount(a)) assert.NoError(t, a.CreateFunnel(f)) a.Reset() // Track: "john" completes the whole checkout. assert.NoError(t, a.Track(newTestEvent("2000-01-01T00:00:00Z", "john", "", "web", "/home", "view", nil))) assert.NoError(t, a.Track(newTestEvent("2000-01-01T00:00:30Z", "john", "", "web", "/about", "view", nil))) assert.NoError(t, a.Track(newTestEvent("2000-01-01T00:01:00Z", "john", "", "web", "/signup", "view", nil))) assert.NoError(t, a.Track(newTestEvent("2000-01-01T00:02:00Z", "john", "", "web", "/checkout", "view", nil))) // Track: "susy" only completes the first step. assert.NoError(t, a.Track(newTestEvent("2000-01-02T00:00:00Z", "susy", "", "web", "/home", "view", nil))) // Track: "jim" completes the whole checkout but not in one session. assert.NoError(t, a.Track(newTestEvent("2000-01-01T00:00:00Z", "jim", "", "web", "/home", "view", nil))) assert.NoError(t, a.Track(newTestEvent("2000-01-01T00:01:00Z", "jim", "", "web", "/signup", "view", nil))) assert.NoError(t, a.Track(newTestEvent("2000-01-10T00:00:00Z", "jim", "", "web", "/checkout", "view", nil))) // Execute funnel query. results, err := f.Query() assert.NoError(t, err) if assert.NotNil(t, results) && assert.Equal(t, len(results.Steps), 3) { assert.Equal(t, results.Name, "FUN") assert.Equal(t, results.Steps[0].Condition, "resource == '/home'") assert.Equal(t, results.Steps[0].Count, 3) // john, susy, jim assert.Equal(t, results.Steps[1].Condition, "resource == '/signup'") assert.Equal(t, results.Steps[1].Count, 2) // john, jim assert.Equal(t, results.Steps[2].Condition, "resource == '/checkout'") assert.Equal(t, results.Steps[2].Count, 1) // john } return nil }) }) }
// Ensure that a funnel can be deleted. func TestFunnelDelete(t *testing.T) { withDB(func(db *DB) { db.Do(func(tx *Tx) error { // Create account and funnel. a := &Account{} assert.NoError(t, tx.CreateAccount(a)) f := &Funnel{Name: "Funnel Y", Steps: []*FunnelStep{{Condition: "action == 'foo'"}}} assert.NoError(t, a.CreateFunnel(f)) // Delete the funnel. assert.NoError(t, f.Delete()) // Retrieve the funnel again. _, err := tx.Funnel(1) assert.Equal(t, err, ErrFunnelNotFound) return nil }) }) }
// Ensure that the database can create an account. func TestDBCreateAccount(t *testing.T) { withDB(func(db *DB) { db.Do(func(tx *Tx) error { // Create an account. a := &Account{} err := tx.CreateAccount(a) assert.NoError(t, err) assert.Equal(t, tx, a.Tx) assert.Equal(t, a.ID(), 1) // Retrieve the account. a2, err := tx.Account(1) if assert.NoError(t, err) && assert.NotNil(t, a2) { assert.Equal(t, tx, a2.Tx) assert.Equal(t, a2.ID(), 1) } assert.True(t, a != a2) return nil }) }) }
// Ensure that a funnel can update itself. func TestFunnelUpdate(t *testing.T) { withDB(func(db *DB) { db.Do(func(tx *Tx) error { a := &Account{} assert.NoError(t, tx.CreateAccount(a)) f := &Funnel{Name: "Funnel Y", Steps: []*FunnelStep{{Condition: "action == 'foo'"}}} assert.NoError(t, a.CreateFunnel(f)) // Update the funnel. f.Name = "Funnel Z" f.Save() // Retrieve the funnel. f2, err := tx.Funnel(1) if assert.NoError(t, err) && assert.NotNil(t, f2) { assert.Equal(t, f2.Name, "Funnel Z") } return nil }) }) }
// Ensure that an account can track events and insert them into Sky. func TestAccountTrack(t *testing.T) { withDB(func(db *DB) { db.Do(func(tx *Tx) error { a := &Account{} tx.CreateAccount(a) a.Reset() // Add some events. assert.NoError(t, a.Track(newTestEvent("2000-01-01T00:00:00Z", "john", "DEV0", "web", "/", "view", nil))) assert.NoError(t, a.Track(newTestEvent("2000-01-01T00:00:05Z", "john", "DEV1", "web", "/signup", "view", nil))) assert.NoError(t, a.Track(newTestEvent("2000-01-01T00:00:00Z", "susy", "DEV2", "web", "/cancel", "click", nil))) // Verify "john" events. events, err := a.Events("@john") assert.NoError(t, err) if assert.Equal(t, len(events), 2) { assert.Equal(t, events[0].Timestamp, mustParseTime("2000-01-01T00:00:00Z")) assert.Equal(t, events[0].Channel, "web") assert.Equal(t, events[0].Resource, "/") assert.Equal(t, events[0].Action, "view") assert.Equal(t, events[1].Timestamp, mustParseTime("2000-01-01T00:00:05Z")) assert.Equal(t, events[1].Channel, "web") assert.Equal(t, events[1].Resource, "/signup") assert.Equal(t, events[1].Action, "view") } // Verify "susy" events. events, err = a.Events("@susy") assert.NoError(t, err) if assert.Equal(t, len(events), 1) { assert.Equal(t, events[0].Timestamp, mustParseTime("2000-01-01T00:00:00Z")) assert.Equal(t, events[0].Channel, "web") assert.Equal(t, events[0].Resource, "/cancel") assert.Equal(t, events[0].Action, "click") } return nil }) }) }
// Ensure that an account can create a funnel. func TestFunnelCreate(t *testing.T) { withDB(func(db *DB) { db.Do(func(tx *Tx) error { // Create an account and funnel. a := &Account{} assert.NoError(t, tx.CreateAccount(a)) f := &Funnel{Name: "Funnel Y", Steps: []*FunnelStep{{Condition: "action == 'foo'"}}} assert.NoError(t, a.CreateFunnel(f)) assert.Equal(t, f.ID(), 1) // Retrieve the funnel. f2, err := tx.Funnel(1) if assert.NoError(t, err) && assert.NotNil(t, f2) { assert.Equal(t, f2.Tx, tx) assert.Equal(t, f2.ID(), 1) assert.Equal(t, f2.AccountID, 1) assert.Equal(t, f2.Name, "Funnel Y") } return nil }) }) }
// Ensure that the database can return all accounts. func TestDBAccounts(t *testing.T) { withDB(func(db *DB) { db.Do(func(tx *Tx) error { tx.CreateAccount(&Account{}) tx.CreateAccount(&Account{}) tx.CreateAccount(&Account{}) // Retrieve the accounts. accounts, err := tx.Accounts() if assert.NoError(t, err) && assert.Equal(t, len(accounts), 3) { assert.Equal(t, accounts[0].Tx, tx) assert.Equal(t, accounts[0].ID(), 1) assert.Equal(t, accounts[1].Tx, tx) assert.Equal(t, accounts[1].ID(), 2) assert.Equal(t, accounts[2].Tx, tx) assert.Equal(t, accounts[2].ID(), 3) } return nil }) }) }