package user import ( goerrors "errors" "regexp" "testing" "npm/internal/errors" "npm/internal/model" "npm/internal/test" "github.com/DATA-DOG/go-sqlmock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) // +------------+ // | Setup | // +------------+ type testsuite struct { suite.Suite mock sqlmock.Sqlmock singleRow *sqlmock.Rows capabilitiesRows *sqlmock.Rows listCountRows *sqlmock.Rows listRows *sqlmock.Rows } // SetupTest is executed before each test func (s *testsuite) SetupTest() { var err error s.mock, err = test.Setup() require.NoError(s.T(), err) // These rows need to be intantiated for each test as they are // read in the db object, and their row position is not resettable // between tests. s.singleRow = sqlmock.NewRows([]string{ "id", "name", "nickname", "email", "is_disabled", "is_system", }).AddRow( 10, "John Doe", "Jonny", "jon@example.com", false, false, ) s.capabilitiesRows = sqlmock.NewRows([]string{ "user_id", "capability_name", }).AddRow( 10, "hosts.view", ).AddRow( 10, "hosts.manage", ) s.listCountRows = sqlmock.NewRows([]string{ "count(*)", }).AddRow( 2, ) s.listRows = sqlmock.NewRows([]string{ "id", "name", "nickname", "email", "is_disabled", "is_system", }).AddRow( 10, "John Doe", "Jonny", "jon@example.com", false, false, ).AddRow( 11, "Jane Doe", "Jane", "jane@example.com", true, false, ) } // In order for 'go test' to run this suite, we need to create // a normal test function and pass our suite to suite.Run func TestExampleTestSuite(t *testing.T) { suite.Run(t, new(testsuite)) } func assertModel(t *testing.T, m Model) { assert.Equal(t, uint(10), m.ID) assert.Equal(t, "John Doe", m.Name) assert.Equal(t, "Jonny", m.Nickname) assert.Equal(t, "jon@example.com", m.Email) assert.Equal(t, false, m.IsDisabled) assert.Equal(t, false, m.IsSystem) } // +------------+ // | Tests | // +------------+ func (s *testsuite) TestGetByID() { s.mock. ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "user" WHERE "user"."id" = $1 AND "user"."is_deleted" = $2 ORDER BY "user"."id" LIMIT 1`)). WithArgs(10, 0). WillReturnRows(s.singleRow) m, err := GetByID(10) require.NoError(s.T(), err) require.NoError(s.T(), s.mock.ExpectationsWereMet()) assertModel(s.T(), m) } func (s *testsuite) TestLoadByEmail() { s.mock. ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "user" WHERE email = $1 AND is_system = $2 AND "user"."is_deleted" = $3 ORDER BY "user"."id" LIMIT 1`)). WithArgs("jon@example.com", false, 0). WillReturnRows(s.singleRow) m, err := GetByEmail("jon@example.com") require.NoError(s.T(), err) require.NoError(s.T(), s.mock.ExpectationsWereMet()) assertModel(s.T(), m) } func (s *testsuite) TestIsEnabled() { s.mock. ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "user" WHERE "user"."id" = $1 AND "user"."is_deleted" = $2 ORDER BY "user"."id" LIMIT 1`)). WithArgs(10, 0). WillReturnRows(s.singleRow) s.mock. ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "user" WHERE "user"."id" = $1 AND "user"."is_deleted" = $2 ORDER BY "user"."id" LIMIT 1`)). WithArgs(999, 0). WillReturnError(goerrors.New("record not found")) // user that exists exists, enabled, err := IsEnabled(10) require.NoError(s.T(), err) assert.Equal(s.T(), true, exists) assert.Equal(s.T(), true, enabled) // that that doesn't exist exists, enabled, err = IsEnabled(999) assert.Equal(s.T(), "record not found", err.Error()) assert.Equal(s.T(), false, exists) assert.Equal(s.T(), false, enabled) require.NoError(s.T(), s.mock.ExpectationsWereMet()) } func (s *testsuite) TestSave() { s.mock. ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "user" WHERE email = $1 AND is_system = $2 AND "user"."is_deleted" = $3 ORDER BY "user"."id" LIMIT 1`)). WithArgs("jon@example.com", false, 0). WillReturnRows(s.singleRow) s.mock.ExpectBegin() s.mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "user" ("created_at","updated_at","is_deleted","name","nickname","email","is_disabled","is_system") VALUES ($1,$2,$3,$4,$5,$6,$7,$8) RETURNING "id"`)). WithArgs( sqlmock.AnyArg(), sqlmock.AnyArg(), 0, "John Doe", "Jonny", "sarah@example.com", false, false, ). WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow("11")) s.mock.ExpectCommit() // New model, as system m := Model{ Name: "John Doe", Nickname: "Jonny", Email: "JON@example.com", // mixed case on purpose IsSystem: true, } err := m.Save() assert.Equal(s.T(), errors.ErrSystemUserReadonly.Error(), err.Error()) // Remove system and try again. Expect error due to duplicate email m.IsSystem = false err = m.Save() assert.Equal(s.T(), errors.ErrDuplicateEmailUser.Error(), err.Error()) // Change email and try again. Expect success m.Email = "sarah@example.com" err = m.Save() require.NoError(s.T(), err) require.NoError(s.T(), s.mock.ExpectationsWereMet()) } func (s *testsuite) TestDelete() { s.mock.ExpectBegin() s.mock. ExpectExec(regexp.QuoteMeta(`UPDATE "user" SET "is_deleted"=$1 WHERE "user"."id" = $2 AND "user"."is_deleted" = $3`)). WithArgs(1, 10, 0). WillReturnResult(sqlmock.NewResult(0, 1)) s.mock.ExpectCommit() m := Model{} err := m.Delete() assert.Equal(s.T(), "Unable to delete a new object", err.Error()) m2 := Model{ ModelBase: model.ModelBase{ ID: 10, }, Name: "John Doe", } err2 := m2.Delete() require.NoError(s.T(), err2) require.NoError(s.T(), s.mock.ExpectationsWereMet()) } func (s *testsuite) TestGenerateGravatar() { m := Model{Email: "jon@example.com"} m.generateGravatar() assert.Equal(s.T(), "https://www.gravatar.com/avatar/dc36565cc2376197358fa27ed4c47253?d=mm&r=pg&s=128", m.GravatarURL) } func (s *testsuite) TestDeleteAll() { s.mock. ExpectExec(regexp.QuoteMeta("DELETE FROM `user` WHERE is_system = $1")). WithArgs(false). WillReturnResult(sqlmock.NewResult(0, 1)) err := DeleteAll() require.NoError(s.T(), err) require.NoError(s.T(), s.mock.ExpectationsWereMet()) } func (s *testsuite) TestGetCapabilities() { s.mock. ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "user_has_capability" WHERE user_id = $1`)). WithArgs(10). WillReturnRows(s.capabilitiesRows) s.mock. ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "user_has_capability" WHERE user_id = $1`)). WithArgs(999). WillReturnRows(sqlmock.NewRows([]string{})) s.mock. ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "user_has_capability" WHERE user_id = $1`)). WithArgs(1000). WillReturnError(goerrors.New("some other error")) // user that exists caps, err := GetCapabilities(10) require.NoError(s.T(), err) assert.Equal(s.T(), 2, len(caps)) // user that doesn't exist caps, err = GetCapabilities(999) require.NoError(s.T(), err) assert.Equal(s.T(), 0, len(caps)) // some other error caps, err = GetCapabilities(1000) assert.Equal(s.T(), "some other error", err.Error()) assert.Equal(s.T(), 0, len(caps)) require.NoError(s.T(), s.mock.ExpectationsWereMet()) } func (s *testsuite) TestList() { s.mock. ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "user" WHERE name LIKE $1 AND "user"."is_deleted" = $2`)). WithArgs("%jon%", 0). WillReturnRows(s.listCountRows) s.mock. ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "user" WHERE name LIKE $1 AND "user"."is_deleted" = $2 ORDER BY name asc LIMIT 8`)). WithArgs("%jon%", 0). WillReturnRows(s.listRows) s.mock. ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "user_has_capability" WHERE user_id = $1`)). WithArgs(10). WillReturnRows(s.capabilitiesRows) s.mock. ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "user_has_capability" WHERE user_id = $1`)). WithArgs(11). WillReturnRows(sqlmock.NewRows([]string{})) p := model.PageInfo{ Offset: 0, Limit: 8, Sort: []model.Sort{ { Field: "name", Direction: "asc", }, }, } f := []model.Filter{ { Field: "name", Modifier: "contains", Value: []string{"jon"}, }, } e := []string{"capabilities"} resp, err := List(p, f, e) require.NoError(s.T(), err) assert.Equal(s.T(), int64(2), resp.Total) assert.Equal(s.T(), p.Offset, resp.Offset) assert.Equal(s.T(), p.Limit, resp.Limit) assert.Equal(s.T(), p.Limit, resp.Limit) assert.Equal(s.T(), p.Sort, resp.Sort) assert.Equal(s.T(), f, resp.Filter) require.NoError(s.T(), s.mock.ExpectationsWereMet()) } func (s *testsuite) TestSetPermissions() { s.mock.ExpectBegin() s.mock. ExpectExec(regexp.QuoteMeta(`DELETE FROM "user_has_capability" WHERE user_id = $1`)). WithArgs(10). WillReturnResult(sqlmock.NewResult(0, 1)) s.mock.ExpectCommit() s.mock.ExpectBegin() s.mock. ExpectExec(regexp.QuoteMeta(`INSERT INTO "user_has_capability" ("user_id","capability_name") VALUES ($1,$2),($3,$4)`)). WithArgs(10, "hosts.view", 10, "hosts.manage"). WillReturnResult(sqlmock.NewResult(88, 0)) s.mock.ExpectCommit() // Empty model returns error m := Model{} err := m.SetPermissions([]string{"hosts.view", "hosts.manage"}) assert.Equal(s.T(), "Cannot set permissions without first saving the User", err.Error()) // Defined user m.ID = 10 err = m.SetPermissions([]string{"hosts.view", "hosts.manage"}) require.NoError(s.T(), err) require.NoError(s.T(), s.mock.ExpectationsWereMet()) } func (s *testsuite) TestSaveCapabilities() { s.mock. ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "capability"`)). WillReturnRows(sqlmock.NewRows([]string{"name"}). AddRow("full-admin"). AddRow("hosts.view"). AddRow("hosts.manage")) s.mock.ExpectBegin() s.mock. ExpectExec(regexp.QuoteMeta(`DELETE FROM "user_has_capability" WHERE user_id = $1`)). WithArgs(10). WillReturnResult(sqlmock.NewResult(0, 1)) s.mock.ExpectCommit() s.mock.ExpectBegin() s.mock. ExpectExec(regexp.QuoteMeta(`INSERT INTO "user_has_capability" ("user_id","capability_name") VALUES ($1,$2),($3,$4)`)). WithArgs(10, "hosts.view", 10, "hosts.manage"). WillReturnResult(sqlmock.NewResult(88, 0)) s.mock.ExpectCommit() // Empty model returns error m := Model{} err := m.SaveCapabilities() assert.Equal(s.T(), "Cannot save capabilities on unsaved user", err.Error()) // Empty model returns error m.ID = 10 err = m.SaveCapabilities() assert.Equal(s.T(), "At least 1 capability required for a user", err.Error()) // With some caps m.Capabilities = []string{"hosts.view", "hosts.manage"} err = m.SaveCapabilities() require.NoError(s.T(), err) require.NoError(s.T(), s.mock.ExpectationsWereMet()) } func (s *testsuite) TestSaveCapabilitiesInvalid() { s.mock. ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "capability"`)). WillReturnRows(sqlmock.NewRows([]string{"name"}). AddRow("full-admin"). AddRow("hosts.view"). AddRow("hosts.manage")) // Empty model returns error m := Model{ ModelBase: model.ModelBase{ ID: 10, }, Capabilities: []string{"doesnotexist", "hosts.manage"}, } err := m.SaveCapabilities() assert.Equal(s.T(), "Capability `doesnotexist` is not valid", err.Error()) require.NoError(s.T(), s.mock.ExpectationsWereMet()) }