mirror of
				https://github.com/NginxProxyManager/nginx-proxy-manager.git
				synced 2025-10-30 23:33:34 +00:00 
			
		
		
		
	More unit tests and html coverage report
also fixes a limit bug on listquery
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -8,6 +8,7 @@ bin/* | |||||||
| backend/config.json | backend/config.json | ||||||
| backend/embed/assets | backend/embed/assets | ||||||
| backend/.task | backend/.task | ||||||
|  | backend/coverage.html | ||||||
| test/node_modules | test/node_modules | ||||||
| */node_modules | */node_modules | ||||||
| docs/.vuepress/dist | docs/.vuepress/dist | ||||||
|   | |||||||
| @@ -49,7 +49,7 @@ func Enforce(permission string) func(http.Handler) http.Handler { | |||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				userID := uint(claims["uid"].(float64)) | 				userID := uint(claims["uid"].(float64)) | ||||||
| 				_, enabled := user.IsEnabled(userID) | 				_, enabled, _ := user.IsEnabled(userID) | ||||||
| 				if token == nil || !enabled { | 				if token == nil || !enabled { | ||||||
| 					h.ResultErrorJSON(w, r, http.StatusUnauthorized, "Unauthorised", nil) | 					h.ResultErrorJSON(w, r, http.StatusUnauthorized, "Unauthorised", nil) | ||||||
| 					return | 					return | ||||||
|   | |||||||
| @@ -32,7 +32,7 @@ func SSEAuth(next http.Handler) http.Handler { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		userID := uint(claims["uid"].(float64)) | 		userID := uint(claims["uid"].(float64)) | ||||||
| 		_, enabled := user.IsEnabled(userID) | 		_, enabled, _ := user.IsEnabled(userID) | ||||||
| 		if token == nil || !enabled { | 		if token == nil || !enabled { | ||||||
| 			h.ResultErrorJSON(w, r, http.StatusUnauthorized, "Unauthorised", nil) | 			h.ResultErrorJSON(w, r, http.StatusUnauthorized, "Unauthorised", nil) | ||||||
| 			return | 			return | ||||||
|   | |||||||
| @@ -30,8 +30,10 @@ func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Get rows | 	// Get rows | ||||||
|  | 	dbo = entity.AddOffsetLimitToList(dbo, &pageInfo) | ||||||
|  | 	dbo = entity.AddOrderToList(dbo, pageInfo.Sort, defaultSort) | ||||||
| 	items := make([]Model, 0) | 	items := make([]Model, 0) | ||||||
| 	if res := entity.AddOrderToList(dbo, &pageInfo, defaultSort).Find(&items); res.Error != nil { | 	if res := dbo.Find(&items); res.Error != nil { | ||||||
| 		return result, res.Error | 		return result, res.Error | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -46,8 +46,10 @@ func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (ent | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Get rows | 	// Get rows | ||||||
|  | 	dbo = entity.AddOffsetLimitToList(dbo, &pageInfo) | ||||||
|  | 	dbo = entity.AddOrderToList(dbo, pageInfo.Sort, defaultSort) | ||||||
| 	items := make([]Model, 0) | 	items := make([]Model, 0) | ||||||
| 	if res := entity.AddOrderToList(dbo, &pageInfo, defaultSort).Find(&items); res.Error != nil { | 	if res := dbo.Find(&items); res.Error != nil { | ||||||
| 		return result, res.Error | 		return result, res.Error | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -30,8 +30,10 @@ func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Get rows | 	// Get rows | ||||||
|  | 	dbo = entity.AddOffsetLimitToList(dbo, &pageInfo) | ||||||
|  | 	dbo = entity.AddOrderToList(dbo, pageInfo.Sort, defaultSort) | ||||||
| 	items := make([]Model, 0) | 	items := make([]Model, 0) | ||||||
| 	if res := entity.AddOrderToList(dbo, &pageInfo, defaultSort).Find(&items); res.Error != nil { | 	if res := dbo.Find(&items); res.Error != nil { | ||||||
| 		return result, res.Error | 		return result, res.Error | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -30,8 +30,10 @@ func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Get rows | 	// Get rows | ||||||
|  | 	dbo = entity.AddOffsetLimitToList(dbo, &pageInfo) | ||||||
|  | 	dbo = entity.AddOrderToList(dbo, pageInfo.Sort, defaultSort) | ||||||
| 	items := make([]Model, 0) | 	items := make([]Model, 0) | ||||||
| 	if res := entity.AddOrderToList(dbo, &pageInfo, defaultSort).Find(&items); res.Error != nil { | 	if res := dbo.Find(&items); res.Error != nil { | ||||||
| 		return result, res.Error | 		return result, res.Error | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -32,8 +32,10 @@ func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (ent | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Get rows | 	// Get rows | ||||||
|  | 	dbo = entity.AddOffsetLimitToList(dbo, &pageInfo) | ||||||
|  | 	dbo = entity.AddOrderToList(dbo, pageInfo.Sort, defaultSort) | ||||||
| 	items := make([]Model, 0) | 	items := make([]Model, 0) | ||||||
| 	if res := entity.AddOrderToList(dbo, &pageInfo, defaultSort).Find(&items); res.Error != nil { | 	if res := dbo.Find(&items); res.Error != nil { | ||||||
| 		return result, res.Error | 		return result, res.Error | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,11 +7,6 @@ import ( | |||||||
| 	"gorm.io/gorm" | 	"gorm.io/gorm" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // ListableModel is a interface for common use |  | ||||||
| type ListableModel interface { |  | ||||||
| 	TableName() string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ListResponse is the JSON response for users list | // ListResponse is the JSON response for users list | ||||||
| type ListResponse struct { | type ListResponse struct { | ||||||
| 	Total  int64          `json:"total"` | 	Total  int64          `json:"total"` | ||||||
| @@ -29,7 +24,6 @@ func ListQueryBuilder( | |||||||
| 	filterMap map[string]model.FilterMapValue, | 	filterMap map[string]model.FilterMapValue, | ||||||
| ) *gorm.DB { | ) *gorm.DB { | ||||||
| 	scopes := make([]func(*gorm.DB) *gorm.DB, 0) | 	scopes := make([]func(*gorm.DB) *gorm.DB, 0) | ||||||
| 	scopes = append(scopes, ScopeOffsetLimit(pageInfo)) |  | ||||||
| 	scopes = append(scopes, ScopeFilters(filters, filterMap)) | 	scopes = append(scopes, ScopeFilters(filters, filterMap)) | ||||||
| 	return database.GetDB().Scopes(scopes...) | 	return database.GetDB().Scopes(scopes...) | ||||||
| } | } | ||||||
| @@ -38,8 +32,16 @@ func ListQueryBuilder( | |||||||
| // Postgres in particular doesn't like count(*) when ordering at the same time | // Postgres in particular doesn't like count(*) when ordering at the same time | ||||||
| func AddOrderToList( | func AddOrderToList( | ||||||
| 	dbo *gorm.DB, | 	dbo *gorm.DB, | ||||||
| 	pageInfo *model.PageInfo, | 	sort []model.Sort, | ||||||
| 	defaultSort model.Sort, | 	defaultSort model.Sort, | ||||||
| ) *gorm.DB { | ) *gorm.DB { | ||||||
| 	return dbo.Scopes(ScopeOrderBy(pageInfo, defaultSort)) | 	return dbo.Scopes(ScopeOrderBy(sort, defaultSort)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // AddOffsetLimitToList is used after query above is used for pagination | ||||||
|  | func AddOffsetLimitToList( | ||||||
|  | 	dbo *gorm.DB, | ||||||
|  | 	pageInfo *model.PageInfo, | ||||||
|  | ) *gorm.DB { | ||||||
|  | 	return dbo.Scopes(ScopeOffsetLimit(pageInfo)) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -30,8 +30,10 @@ func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Get rows | 	// Get rows | ||||||
|  | 	dbo = entity.AddOffsetLimitToList(dbo, &pageInfo) | ||||||
|  | 	dbo = entity.AddOrderToList(dbo, pageInfo.Sort, defaultSort) | ||||||
| 	items := make([]Model, 0) | 	items := make([]Model, 0) | ||||||
| 	if res := entity.AddOrderToList(dbo, &pageInfo, defaultSort).Find(&items); res.Error != nil { | 	if res := dbo.Find(&items); res.Error != nil { | ||||||
| 		return result, res.Error | 		return result, res.Error | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -19,11 +19,11 @@ func ScopeOffsetLimit(pageInfo *model.PageInfo) func(db *gorm.DB) *gorm.DB { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func ScopeOrderBy(pageInfo *model.PageInfo, defaultSort model.Sort) func(db *gorm.DB) *gorm.DB { | func ScopeOrderBy(sort []model.Sort, defaultSort model.Sort) func(db *gorm.DB) *gorm.DB { | ||||||
| 	return func(db *gorm.DB) *gorm.DB { | 	return func(db *gorm.DB) *gorm.DB { | ||||||
| 		if pageInfo.Sort != nil { | 		if sort != nil { | ||||||
| 			// Sort by items in slice | 			// Sort by items in slice | ||||||
| 			return db.Order(sortToOrderString(pageInfo.Sort)) | 			return db.Order(sortToOrderString(sort)) | ||||||
| 		} else if defaultSort.Field != "" { | 		} else if defaultSort.Field != "" { | ||||||
| 			// Default to this sort | 			// Default to this sort | ||||||
| 			str := defaultSort.Field | 			str := defaultSort.Field | ||||||
|   | |||||||
| @@ -37,8 +37,10 @@ func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Get rows | 	// Get rows | ||||||
|  | 	dbo = entity.AddOffsetLimitToList(dbo, &pageInfo) | ||||||
|  | 	dbo = entity.AddOrderToList(dbo, pageInfo.Sort, defaultSort) | ||||||
| 	items := make([]Model, 0) | 	items := make([]Model, 0) | ||||||
| 	if res := entity.AddOrderToList(dbo, &pageInfo, defaultSort).Find(&items); res.Error != nil { | 	if res := dbo.Find(&items); res.Error != nil { | ||||||
| 		return result, res.Error | 		return result, res.Error | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -30,8 +30,10 @@ func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Get rows | 	// Get rows | ||||||
|  | 	dbo = entity.AddOffsetLimitToList(dbo, &pageInfo) | ||||||
|  | 	dbo = entity.AddOrderToList(dbo, pageInfo.Sort, defaultSort) | ||||||
| 	items := make([]Model, 0) | 	items := make([]Model, 0) | ||||||
| 	if res := entity.AddOrderToList(dbo, &pageInfo, defaultSort).Find(&items); res.Error != nil { | 	if res := dbo.Find(&items); res.Error != nil { | ||||||
| 		return result, res.Error | 		return result, res.Error | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -30,8 +30,10 @@ func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (ent | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Get rows | 	// Get rows | ||||||
|  | 	dbo = entity.AddOffsetLimitToList(dbo, &pageInfo) | ||||||
|  | 	dbo = entity.AddOrderToList(dbo, pageInfo.Sort, defaultSort) | ||||||
| 	items := make([]Model, 0) | 	items := make([]Model, 0) | ||||||
| 	if res := entity.AddOrderToList(dbo, &pageInfo, defaultSort).Find(&items); res.Error != nil { | 	if res := dbo.Find(&items); res.Error != nil { | ||||||
| 		return result, res.Error | 		return result, res.Error | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -39,8 +39,10 @@ func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Get rows | 	// Get rows | ||||||
|  | 	dbo = entity.AddOffsetLimitToList(dbo, &pageInfo) | ||||||
|  | 	dbo = entity.AddOrderToList(dbo, pageInfo.Sort, defaultSort) | ||||||
| 	items := make([]Model, 0) | 	items := make([]Model, 0) | ||||||
| 	if res := entity.AddOrderToList(dbo, &pageInfo, defaultSort).Find(&items); res.Error != nil { | 	if res := dbo.Find(&items); res.Error != nil { | ||||||
| 		return result, res.Error | 		return result, res.Error | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										421
									
								
								backend/internal/entity/user/entity_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										421
									
								
								backend/internal/entity/user/entity_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,421 @@ | |||||||
|  | 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()) | ||||||
|  | } | ||||||
| @@ -23,14 +23,14 @@ func GetByEmail(email string) (Model, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| // IsEnabled is used by middleware to ensure the user is still enabled | // IsEnabled is used by middleware to ensure the user is still enabled | ||||||
| // returns (userExist, isEnabled) | // returns (userExist, isEnabled, error) | ||||||
| func IsEnabled(userID uint) (bool, bool) { | func IsEnabled(userID uint) (bool, bool, error) { | ||||||
| 	var user Model | 	var user Model | ||||||
| 	db := database.GetDB() | 	db := database.GetDB() | ||||||
| 	if result := db.First(&user, userID); result.Error != nil { | 	if result := db.First(&user, userID); result.Error != nil { | ||||||
| 		return false, false | 		return false, false, result.Error | ||||||
| 	} | 	} | ||||||
| 	return true, !user.IsDisabled | 	return true, !user.IsDisabled, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // List will return a list of users | // List will return a list of users | ||||||
| @@ -51,8 +51,10 @@ func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (ent | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Get rows | 	// Get rows | ||||||
|  | 	dbo = entity.AddOffsetLimitToList(dbo, &pageInfo) | ||||||
|  | 	dbo = entity.AddOrderToList(dbo, pageInfo.Sort, defaultSort) | ||||||
| 	items := make([]Model, 0) | 	items := make([]Model, 0) | ||||||
| 	if res := entity.AddOrderToList(dbo, &pageInfo, defaultSort).Find(&items); res.Error != nil { | 	if res := dbo.Find(&items); res.Error != nil { | ||||||
| 		return result, res.Error | 		return result, res.Error | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,154 +0,0 @@ | |||||||
| package user |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"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 |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // SetupTest is executed before each test |  | ||||||
| func (s *testsuite) SetupTest() { |  | ||||||
| 	var err error |  | ||||||
| 	s.mock, err = test.Setup() |  | ||||||
| 	require.NoError(s.T(), err) |  | ||||||
|  |  | ||||||
| 	s.singleRow = sqlmock.NewRows([]string{ |  | ||||||
| 		"id", |  | ||||||
| 		"name", |  | ||||||
| 		"nickname", |  | ||||||
| 		"email", |  | ||||||
| 		"is_disabled", |  | ||||||
| 		"is_system", |  | ||||||
| 	}).AddRow( |  | ||||||
| 		10, |  | ||||||
| 		"John Doe", |  | ||||||
| 		"Jonny", |  | ||||||
| 		"jon@example.com", |  | ||||||
| 		false, |  | ||||||
| 		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)) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // +------------+ |  | ||||||
| // | Tests      | |  | ||||||
| // +------------+ |  | ||||||
|  |  | ||||||
| func (s *testsuite) TestLoadByID() { |  | ||||||
| 	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 := Model{} |  | ||||||
| 	err := m.LoadByID(10) |  | ||||||
| 	require.NoError(s.T(), err) |  | ||||||
| 	require.NoError(s.T(), s.mock.ExpectationsWereMet()) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| 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 := Model{} |  | ||||||
| 	err := m.LoadByEmail("jon@example.com") |  | ||||||
| 	require.NoError(s.T(), err) |  | ||||||
| 	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) |  | ||||||
| } |  | ||||||
| @@ -1,3 +1,7 @@ | |||||||
| #!/bin/bash -e | #!/bin/bash -e | ||||||
|  |  | ||||||
| go test -json -cover ./internal/... | tparse | DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||||||
|  |  | ||||||
|  | go test -json -cover -coverprofile="$DIR/../coverage.out" ./internal/... | tparse | ||||||
|  | go tool cover -html="$DIR/../coverage.out" -o "$DIR/../coverage.html" | ||||||
|  | rm -f "$DIR/../coverage.out" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user