From 98a71259de7ade4901ed9d7a14e1d40581e1eefd Mon Sep 17 00:00:00 2001 From: Emil Shakirov Date: Mon, 1 Jun 2020 23:31:16 +0200 Subject: [PATCH 1/3] Use interface in youtubeapi --- cmd/container.go | 2 +- cmd/youtube_api/youtube_api.go | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/cmd/container.go b/cmd/container.go index 2253672..06a5339 100644 --- a/cmd/container.go +++ b/cmd/container.go @@ -9,5 +9,5 @@ type Container struct { TimecodeRepository TimecodeRepository UserRepository UserRepository - YoutubeAPI *youtubeapi.Service + YoutubeAPI youtubeapi.IService } diff --git a/cmd/youtube_api/youtube_api.go b/cmd/youtube_api/youtube_api.go index 39b7f19..c687c55 100644 --- a/cmd/youtube_api/youtube_api.go +++ b/cmd/youtube_api/youtube_api.go @@ -11,7 +11,14 @@ import ( const GOOGLE_API_KEY = "GOOGLE_API_KEY" +type IService interface { + FetchVideoDescription(string) string + FetchVideoComments(string) []string +} + type Service struct { + IService + client *youtube.Service } From 64ca6f214b7b4d327e004729bb2ce48972d67da4 Mon Sep 17 00:00:00 2001 From: Emil Shakirov Date: Mon, 1 Jun 2020 23:32:10 +0200 Subject: [PATCH 2/3] Remove global variables from repository tests --- cmd/main_test.go | 14 +++---- cmd/timecode_like_repository_test.go | 41 +++++++++++++-------- cmd/timecode_repository_test.go | 55 +++++++++++++++++----------- cmd/user_repository_test.go | 20 +++++++--- 4 files changed, 78 insertions(+), 52 deletions(-) diff --git a/cmd/main_test.go b/cmd/main_test.go index 299b75b..c98df92 100644 --- a/cmd/main_test.go +++ b/cmd/main_test.go @@ -4,25 +4,21 @@ import ( "context" "net/http" "net/http/httptest" - "os" "testing" "github.com/khaiql/dbcleaner/engine" "gopkg.in/khaiql/dbcleaner.v2" ) -var Cleaner = dbcleaner.New() -var TestDB = initDB() +func createDBCleaner(t *testing.T) dbcleaner.DbCleaner { + t.Helper() -func TestMain(m *testing.M) { + cleaner := dbcleaner.New() dsn := getEnvDSN() pg := engine.NewPostgresEngine(dsn.String()) - Cleaner.SetEngine(pg) - - runMigrations(TestDB) - defer TestDB.Close() + cleaner.SetEngine(pg) - os.Exit(m.Run()) + return cleaner } func executeRequest(t *testing.T, router http.Handler, req *http.Request, user *User) *httptest.ResponseRecorder { diff --git a/cmd/timecode_like_repository_test.go b/cmd/timecode_like_repository_test.go index aa7a23f..feb6ebc 100644 --- a/cmd/timecode_like_repository_test.go +++ b/cmd/timecode_like_repository_test.go @@ -6,39 +6,48 @@ import ( "github.com/jinzhu/gorm" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" + "gopkg.in/khaiql/dbcleaner.v2" ) +var tablesForCleaning = []string{ + "timecode_likes", + "timecodes", + "users", +} + type TimecodeLikeRepositorySuite struct { suite.Suite - DB *gorm.DB - Repo *DBTimecodeLikeRepository + Cleaner dbcleaner.DbCleaner + DB *gorm.DB + Repo *DBTimecodeLikeRepository } func (suite *TimecodeLikeRepositorySuite) SetupSuite() { - suite.DB = TestDB - suite.Repo = &DBTimecodeLikeRepository{DB: TestDB} + cleaner := createDBCleaner(suite.T()) + db := initDB() + runMigrations(db) + + suite.Cleaner = cleaner + suite.DB = db + suite.Repo = &DBTimecodeLikeRepository{DB: db} } func (suite *TimecodeLikeRepositorySuite) SetupTest() { - for _, table := range []string{ - "timecode_likes", - "timecodes", - "users", - } { - Cleaner.Acquire(table) + for _, table := range tablesForCleaning { + suite.Cleaner.Acquire(table) } } func (suite *TimecodeLikeRepositorySuite) TearDownTest() { - for _, table := range []string{ - "timecode_likes", - "timecodes", - "users", - } { - Cleaner.Clean(table) + for _, table := range tablesForCleaning { + suite.Cleaner.Clean(table) } } +func (suite *TimecodeLikeRepositorySuite) TearDownSuite() { + suite.DB.Close() +} + func TestTimecodeLikeRepositorySuite(t *testing.T) { suite.Run(t, new(TimecodeLikeRepositorySuite)) } diff --git a/cmd/timecode_repository_test.go b/cmd/timecode_repository_test.go index 30acf20..d4cbca5 100644 --- a/cmd/timecode_repository_test.go +++ b/cmd/timecode_repository_test.go @@ -7,28 +7,41 @@ import ( "github.com/jinzhu/gorm" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" + "gopkg.in/khaiql/dbcleaner.v2" ) -const videoID = "armenian-dram" -const anotherVideoID = "strategist" - type TimecodeRepositorySuite struct { suite.Suite - DB *gorm.DB - Repo *DBTimecodeRepository + + VideoID string + AnotherVideoID string + Cleaner dbcleaner.DbCleaner + DB *gorm.DB + Repo *DBTimecodeRepository } func (suite *TimecodeRepositorySuite) SetupSuite() { - suite.DB = TestDB - suite.Repo = &DBTimecodeRepository{DB: TestDB} + cleaner := createDBCleaner(suite.T()) + db := initDB() + runMigrations(db) + + suite.VideoID = "armenian-dram" + suite.AnotherVideoID = "strategist" + suite.Cleaner = cleaner + suite.DB = db + suite.Repo = &DBTimecodeRepository{DB: db} } func (suite *TimecodeRepositorySuite) SetupTest() { - Cleaner.Acquire("timecodes") + suite.Cleaner.Acquire("timecodes") } func (suite *TimecodeRepositorySuite) TearDownTest() { - Cleaner.Clean("timecodes") + suite.Cleaner.Clean("timecodes") +} + +func (suite *TimecodeRepositorySuite) TearDownSuite() { + suite.DB.Close() } func TestTimecodeRepositorySuite(t *testing.T) { @@ -39,12 +52,12 @@ func (suite *TimecodeRepositorySuite) TestDBTimecodeRepository_FindByVideoId() { t := suite.T() t.Run("when matching records exist", func(t *testing.T) { - suite.DB.Create(&Timecode{VideoID: videoID, Seconds: 55, Description: "ABC"}) - suite.DB.Create(&Timecode{VideoID: videoID, Seconds: 23, Description: "DEFG"}) - suite.DB.Create(&Timecode{VideoID: anotherVideoID, Seconds: 77, Description: "FGHJ"}) - defer Cleaner.Clean("timecodes") + suite.DB.Create(&Timecode{VideoID: suite.VideoID, Seconds: 55, Description: "ABC"}) + suite.DB.Create(&Timecode{VideoID: suite.VideoID, Seconds: 23, Description: "DEFG"}) + suite.DB.Create(&Timecode{VideoID: suite.AnotherVideoID, Seconds: 77, Description: "FGHJ"}) + defer suite.Cleaner.Clean("timecodes") - timecodes := *suite.Repo.FindByVideoId(videoID) + timecodes := *suite.Repo.FindByVideoId(suite.VideoID) assert.Equal(t, 2, len(timecodes)) assert.Equal(t, 23, timecodes[0].Seconds) @@ -52,9 +65,9 @@ func (suite *TimecodeRepositorySuite) TestDBTimecodeRepository_FindByVideoId() { }) t.Run("when there are no matching records", func(t *testing.T) { - suite.DB.Create(&Timecode{VideoID: anotherVideoID, Seconds: 77, Description: "FGHJ"}) + suite.DB.Create(&Timecode{VideoID: suite.AnotherVideoID, Seconds: 77, Description: "FGHJ"}) - timecodes := *suite.Repo.FindByVideoId(videoID) + timecodes := *suite.Repo.FindByVideoId(suite.VideoID) assert.Equal(t, 0, len(timecodes)) }) @@ -64,20 +77,20 @@ func (suite *TimecodeRepositorySuite) TestDBTimecodeRepository_Create() { t := suite.T() t.Run("when record has been created", func(t *testing.T) { - timecode, err := suite.Repo.Create(&Timecode{VideoID: videoID, Seconds: 55, Description: "ABC"}) + timecode, err := suite.Repo.Create(&Timecode{VideoID: suite.VideoID, Seconds: 55, Description: "ABC"}) assert.Nil(t, err) assert.NotNil(t, timecode.ID) - assert.Equal(t, videoID, timecode.VideoID) + assert.Equal(t, suite.VideoID, timecode.VideoID) }) t.Run("when db returns an error", func(t *testing.T) { seconds := 10 description := "ABC" - suite.DB.Create(&Timecode{VideoID: videoID, Seconds: seconds, Description: description}) + suite.DB.Create(&Timecode{VideoID: suite.VideoID, Seconds: seconds, Description: description}) - timecode, err := suite.Repo.Create(&Timecode{VideoID: videoID, Seconds: seconds, Description: description}) + timecode, err := suite.Repo.Create(&Timecode{VideoID: suite.VideoID, Seconds: seconds, Description: description}) assert.True(t, suite.DB.NewRecord(timecode)) assert.EqualError(t, err, `pq: duplicate key value violates unique constraint "idx_timecodes_seconds_text_video_id"`) @@ -94,7 +107,7 @@ func (suite *TimecodeRepositorySuite) TestDBTimecodeRepository_CreateFromParsedC {Seconds: 56, Description: ""}, } - timecodes := *suite.Repo.CreateFromParsedCodes(parsedTimecodes, videoID) + timecodes := *suite.Repo.CreateFromParsedCodes(parsedTimecodes, suite.VideoID) assert.Equal(t, 2, len(timecodes)) assert.Equal(t, 24, timecodes[0].Seconds) diff --git a/cmd/user_repository_test.go b/cmd/user_repository_test.go index 6988f22..a941ac8 100644 --- a/cmd/user_repository_test.go +++ b/cmd/user_repository_test.go @@ -6,27 +6,35 @@ import ( "github.com/jinzhu/gorm" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" + "gopkg.in/khaiql/dbcleaner.v2" googleAPI "timecodes/cmd/google_api" ) type UserRepositorySuite struct { suite.Suite - DB *gorm.DB - Repo *DBUserRepository + + Cleaner dbcleaner.DbCleaner + DB *gorm.DB + Repo *DBUserRepository } func (suite *UserRepositorySuite) SetupSuite() { - suite.DB = TestDB - suite.Repo = &DBUserRepository{DB: TestDB} + cleaner := createDBCleaner(suite.T()) + db := initDB() + runMigrations(db) + + suite.Cleaner = cleaner + suite.DB = db + suite.Repo = &DBUserRepository{DB: db} } func (suite *UserRepositorySuite) SetupTest() { - Cleaner.Acquire("users") + suite.Cleaner.Acquire("users") } func (suite *UserRepositorySuite) TearDownTest() { - Cleaner.Clean("users") + suite.Cleaner.Clean("users") } func TestUserRepositorySuite(t *testing.T) { From a9d5f16dc364e9248ad203527802fc266bf36986 Mon Sep 17 00:00:00 2001 From: Emil Shakirov Date: Mon, 1 Jun 2020 23:32:26 +0200 Subject: [PATCH 3/3] Add missing tests for timecodes controller --- cmd/timecodes_controller.go | 4 +- cmd/timecodes_controller_test.go | 67 +++++++++++++++++++++++++++++--- 2 files changed, 64 insertions(+), 7 deletions(-) diff --git a/cmd/timecodes_controller.go b/cmd/timecodes_controller.go index 56ce492..6142b69 100644 --- a/cmd/timecodes_controller.go +++ b/cmd/timecodes_controller.go @@ -21,7 +21,7 @@ type TimecodeJSON struct { ID uint `json:"id"` Description string `json:"description"` LikesCount int `json:"likesCount"` - LikedByMe bool `json:"likedByMe"` + LikedByMe bool `json:"likedByMe,omitempty"` Seconds int `json:"seconds"` VideoID string `json:"videoId"` } @@ -67,8 +67,10 @@ func handleCreateTimecode(c *Container, w http.ResponseWriter, r *http.Request) _, err = c.TimecodeRepository.Create(timecode) if err != nil { + w.WriteHeader(http.StatusUnprocessableEntity) json.NewEncoder(w).Encode(err) } else { + w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(serializeTimecode(timecode, currentUser)) } } diff --git a/cmd/timecodes_controller_test.go b/cmd/timecodes_controller_test.go index 995ab45..a872f24 100644 --- a/cmd/timecodes_controller_test.go +++ b/cmd/timecodes_controller_test.go @@ -2,8 +2,10 @@ package main import ( "bytes" + "errors" "net/http" "testing" + "time" timecodeParser "timecodes/cmd/timecode_parser" "github.com/stretchr/testify/assert" @@ -11,7 +13,9 @@ import ( ) var mockTimecodeRepo = &MockTimecodeRepository{} +var mockYTAPI = &mockYT{} var timecodesContainer = &Container{ + YoutubeAPI: mockYTAPI, TimecodeRepository: mockTimecodeRepo, } var timecodesRouter = createRouter(timecodesContainer) @@ -28,7 +32,7 @@ func (m *MockTimecodeRepository) FindByVideoId(videoID string) *[]*Timecode { return collection } - collection = &[]*Timecode{{}, {}, {}} + collection = &[]*Timecode{{}, {}, {Likes: []TimecodeLike{{UserID: 1}}}} return collection } @@ -47,12 +51,32 @@ func (m *MockTimecodeRepository) Create(timecode *Timecode) (*Timecode, error) { return timecode, nil } -func (m *MockTimecodeRepository) CreateFromParsedCodes(parsedCodes []timecodeParser.ParsedTimeCode, videoID string) *[]*Timecode { - args := m.Called(parsedCodes, videoID) +func (m *MockTimecodeRepository) CreateFromParsedCodes(parsedCodes []timecodeParser.ParsedTimeCode, videoId string) *[]*Timecode { + args := m.Called(parsedCodes, videoId) return args.Get(0).(*[]*Timecode) } +type mockYT struct { + mock.Mock +} + +func (m *mockYT) FetchVideoDescription(videoId string) string { + args := m.Called(videoId) + + _ = args.Get(0).(string) + + return "description" +} + +func (m *mockYT) FetchVideoComments(videoId string) []string { + args := m.Called(videoId) + + _ = args.Get(0).([]string) + + return []string{"comment one", "comment two"} +} + func Test_handleGetTimecodes(t *testing.T) { currentUser := &User{} currentUser.ID = 1 @@ -71,15 +95,23 @@ func Test_handleGetTimecodes(t *testing.T) { }) t.Run("when timecodes don't exist", func(t *testing.T) { - t.Skip() timecodes := &[]*Timecode{} + var emptyParsedCodes []timecodeParser.ParsedTimeCode mockTimecodeRepo.On("FindByVideoId", "no-items").Return(timecodes, nil) + mockYTAPI.On("FetchVideoDescription", "no-items").Return("") + mockYTAPI.On("FetchVideoComments", "no-items").Return([]string{}) + mockTimecodeRepo. + On("CreateFromParsedCodes", emptyParsedCodes, "no-items"). + Return(timecodes, nil) - req, _ := http.NewRequest(http.MethodGet, "/timecodes/video-id", nil) + req, _ := http.NewRequest(http.MethodGet, "/timecodes/no-items", nil) response := executeRequest(t, timecodesRouter, req, currentUser) + time.Sleep(1 * time.Millisecond) + + mockYTAPI.AssertExpectations(t) mockTimecodeRepo.AssertExpectations(t) assert.Equal(t, http.StatusOK, response.Code) }) @@ -100,6 +132,29 @@ func Test_handleCreateTimecode(t *testing.T) { response := executeRequest(t, timecodesRouter, req, currentUser) mockTimecodeRepo.AssertExpectations(t) - assert.Equal(t, http.StatusOK, response.Code) + assert.Equal(t, http.StatusCreated, response.Code) + }) + + t.Run("when request params are invalid", func(t *testing.T) { + timecode := &Timecode{VideoID: "video-id", Seconds: 71, Description: ""} + + mockTimecodeRepo.On("Create", timecode).Return(nil, errors.New("")) + + params := []byte(`{ "videoId": "video-id", "seconds": "1:11", "description": "" }`) + req, _ := http.NewRequest(http.MethodPost, "/auth/timecodes", bytes.NewBuffer(params)) + + response := executeRequest(t, timecodesRouter, req, currentUser) + + mockTimecodeRepo.AssertExpectations(t) + assert.Equal(t, http.StatusUnprocessableEntity, response.Code) + }) + + t.Run("when request params contain invalid JSON", func(t *testing.T) { + params := []byte(`{ "Invalid json }`) + req, _ := http.NewRequest(http.MethodPost, "/auth/timecodes", bytes.NewBuffer(params)) + + response := executeRequest(t, timecodesRouter, req, currentUser) + + assert.Equal(t, http.StatusBadRequest, response.Code) }) }