Add test coverage for MediaService.GetAudioSegment

This commit is contained in:
Rob Watson 2021-12-05 20:05:58 +01:00
parent 935c2add2a
commit 6d8b1beba7
11 changed files with 573 additions and 0 deletions

View File

@ -54,6 +54,12 @@ cd backend/
sqlc generate
```
Mocks require [mockery](https://github.com/vektra/mockery) to be installed, and can be regenerated with:
```
go generate
```
### Migrations
Database migrations require [golang-migrate](https://github.com/golang-migrate/migrate).

View File

@ -0,0 +1,166 @@
// Code generated by mockery v2.9.4. DO NOT EDIT.
package mocks
import (
context "context"
mock "github.com/stretchr/testify/mock"
s3 "github.com/aws/aws-sdk-go-v2/service/s3"
)
// S3Client is an autogenerated mock type for the S3Client type
type S3Client struct {
mock.Mock
}
// AbortMultipartUpload provides a mock function with given fields: _a0, _a1, _a2
func (_m *S3Client) AbortMultipartUpload(_a0 context.Context, _a1 *s3.AbortMultipartUploadInput, _a2 ...func(*s3.Options)) (*s3.AbortMultipartUploadOutput, error) {
_va := make([]interface{}, len(_a2))
for _i := range _a2 {
_va[_i] = _a2[_i]
}
var _ca []interface{}
_ca = append(_ca, _a0, _a1)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *s3.AbortMultipartUploadOutput
if rf, ok := ret.Get(0).(func(context.Context, *s3.AbortMultipartUploadInput, ...func(*s3.Options)) *s3.AbortMultipartUploadOutput); ok {
r0 = rf(_a0, _a1, _a2...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*s3.AbortMultipartUploadOutput)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *s3.AbortMultipartUploadInput, ...func(*s3.Options)) error); ok {
r1 = rf(_a0, _a1, _a2...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CompleteMultipartUpload provides a mock function with given fields: _a0, _a1, _a2
func (_m *S3Client) CompleteMultipartUpload(_a0 context.Context, _a1 *s3.CompleteMultipartUploadInput, _a2 ...func(*s3.Options)) (*s3.CompleteMultipartUploadOutput, error) {
_va := make([]interface{}, len(_a2))
for _i := range _a2 {
_va[_i] = _a2[_i]
}
var _ca []interface{}
_ca = append(_ca, _a0, _a1)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *s3.CompleteMultipartUploadOutput
if rf, ok := ret.Get(0).(func(context.Context, *s3.CompleteMultipartUploadInput, ...func(*s3.Options)) *s3.CompleteMultipartUploadOutput); ok {
r0 = rf(_a0, _a1, _a2...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*s3.CompleteMultipartUploadOutput)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *s3.CompleteMultipartUploadInput, ...func(*s3.Options)) error); ok {
r1 = rf(_a0, _a1, _a2...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CreateMultipartUpload provides a mock function with given fields: _a0, _a1, _a2
func (_m *S3Client) CreateMultipartUpload(_a0 context.Context, _a1 *s3.CreateMultipartUploadInput, _a2 ...func(*s3.Options)) (*s3.CreateMultipartUploadOutput, error) {
_va := make([]interface{}, len(_a2))
for _i := range _a2 {
_va[_i] = _a2[_i]
}
var _ca []interface{}
_ca = append(_ca, _a0, _a1)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *s3.CreateMultipartUploadOutput
if rf, ok := ret.Get(0).(func(context.Context, *s3.CreateMultipartUploadInput, ...func(*s3.Options)) *s3.CreateMultipartUploadOutput); ok {
r0 = rf(_a0, _a1, _a2...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*s3.CreateMultipartUploadOutput)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *s3.CreateMultipartUploadInput, ...func(*s3.Options)) error); ok {
r1 = rf(_a0, _a1, _a2...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetObject provides a mock function with given fields: _a0, _a1, _a2
func (_m *S3Client) GetObject(_a0 context.Context, _a1 *s3.GetObjectInput, _a2 ...func(*s3.Options)) (*s3.GetObjectOutput, error) {
_va := make([]interface{}, len(_a2))
for _i := range _a2 {
_va[_i] = _a2[_i]
}
var _ca []interface{}
_ca = append(_ca, _a0, _a1)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *s3.GetObjectOutput
if rf, ok := ret.Get(0).(func(context.Context, *s3.GetObjectInput, ...func(*s3.Options)) *s3.GetObjectOutput); ok {
r0 = rf(_a0, _a1, _a2...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*s3.GetObjectOutput)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *s3.GetObjectInput, ...func(*s3.Options)) error); ok {
r1 = rf(_a0, _a1, _a2...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UploadPart provides a mock function with given fields: _a0, _a1, _a2
func (_m *S3Client) UploadPart(_a0 context.Context, _a1 *s3.UploadPartInput, _a2 ...func(*s3.Options)) (*s3.UploadPartOutput, error) {
_va := make([]interface{}, len(_a2))
for _i := range _a2 {
_va[_i] = _a2[_i]
}
var _ca []interface{}
_ca = append(_ca, _a0, _a1)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *s3.UploadPartOutput
if rf, ok := ret.Get(0).(func(context.Context, *s3.UploadPartInput, ...func(*s3.Options)) *s3.UploadPartOutput); ok {
r0 = rf(_a0, _a1, _a2...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*s3.UploadPartOutput)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *s3.UploadPartInput, ...func(*s3.Options)) error); ok {
r1 = rf(_a0, _a1, _a2...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}

View File

@ -0,0 +1,48 @@
// Code generated by mockery v2.9.4. DO NOT EDIT.
package mocks
import (
context "context"
mock "github.com/stretchr/testify/mock"
s3 "github.com/aws/aws-sdk-go-v2/service/s3"
v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
)
// S3PresignClient is an autogenerated mock type for the S3PresignClient type
type S3PresignClient struct {
mock.Mock
}
// PresignGetObject provides a mock function with given fields: _a0, _a1, _a2
func (_m *S3PresignClient) PresignGetObject(_a0 context.Context, _a1 *s3.GetObjectInput, _a2 ...func(*s3.PresignOptions)) (*v4.PresignedHTTPRequest, error) {
_va := make([]interface{}, len(_a2))
for _i := range _a2 {
_va[_i] = _a2[_i]
}
var _ca []interface{}
_ca = append(_ca, _a0, _a1)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *v4.PresignedHTTPRequest
if rf, ok := ret.Get(0).(func(context.Context, *s3.GetObjectInput, ...func(*s3.PresignOptions)) *v4.PresignedHTTPRequest); ok {
r0 = rf(_a0, _a1, _a2...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v4.PresignedHTTPRequest)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *s3.GetObjectInput, ...func(*s3.PresignOptions)) error); ok {
r1 = rf(_a0, _a1, _a2...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}

View File

@ -0,0 +1,165 @@
// Code generated by mockery v2.9.4. DO NOT EDIT.
package mocks
import (
context "context"
mock "github.com/stretchr/testify/mock"
store "git.netflux.io/rob/clipper/generated/store"
uuid "github.com/google/uuid"
)
// Store is an autogenerated mock type for the Store type
type Store struct {
mock.Mock
}
// CreateMediaSet provides a mock function with given fields: _a0, _a1
func (_m *Store) CreateMediaSet(_a0 context.Context, _a1 store.CreateMediaSetParams) (store.MediaSet, error) {
ret := _m.Called(_a0, _a1)
var r0 store.MediaSet
if rf, ok := ret.Get(0).(func(context.Context, store.CreateMediaSetParams) store.MediaSet); ok {
r0 = rf(_a0, _a1)
} else {
r0 = ret.Get(0).(store.MediaSet)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, store.CreateMediaSetParams) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetMediaSet provides a mock function with given fields: _a0, _a1
func (_m *Store) GetMediaSet(_a0 context.Context, _a1 uuid.UUID) (store.MediaSet, error) {
ret := _m.Called(_a0, _a1)
var r0 store.MediaSet
if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) store.MediaSet); ok {
r0 = rf(_a0, _a1)
} else {
r0 = ret.Get(0).(store.MediaSet)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, uuid.UUID) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetMediaSetByYoutubeID provides a mock function with given fields: _a0, _a1
func (_m *Store) GetMediaSetByYoutubeID(_a0 context.Context, _a1 string) (store.MediaSet, error) {
ret := _m.Called(_a0, _a1)
var r0 store.MediaSet
if rf, ok := ret.Get(0).(func(context.Context, string) store.MediaSet); ok {
r0 = rf(_a0, _a1)
} else {
r0 = ret.Get(0).(store.MediaSet)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// SetEncodedAudioUploaded provides a mock function with given fields: _a0, _a1
func (_m *Store) SetEncodedAudioUploaded(_a0 context.Context, _a1 store.SetEncodedAudioUploadedParams) (store.MediaSet, error) {
ret := _m.Called(_a0, _a1)
var r0 store.MediaSet
if rf, ok := ret.Get(0).(func(context.Context, store.SetEncodedAudioUploadedParams) store.MediaSet); ok {
r0 = rf(_a0, _a1)
} else {
r0 = ret.Get(0).(store.MediaSet)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, store.SetEncodedAudioUploadedParams) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// SetRawAudioUploaded provides a mock function with given fields: _a0, _a1
func (_m *Store) SetRawAudioUploaded(_a0 context.Context, _a1 store.SetRawAudioUploadedParams) (store.MediaSet, error) {
ret := _m.Called(_a0, _a1)
var r0 store.MediaSet
if rf, ok := ret.Get(0).(func(context.Context, store.SetRawAudioUploadedParams) store.MediaSet); ok {
r0 = rf(_a0, _a1)
} else {
r0 = ret.Get(0).(store.MediaSet)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, store.SetRawAudioUploadedParams) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// SetVideoThumbnailUploaded provides a mock function with given fields: _a0, _a1
func (_m *Store) SetVideoThumbnailUploaded(_a0 context.Context, _a1 store.SetVideoThumbnailUploadedParams) (store.MediaSet, error) {
ret := _m.Called(_a0, _a1)
var r0 store.MediaSet
if rf, ok := ret.Get(0).(func(context.Context, store.SetVideoThumbnailUploadedParams) store.MediaSet); ok {
r0 = rf(_a0, _a1)
} else {
r0 = ret.Get(0).(store.MediaSet)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, store.SetVideoThumbnailUploadedParams) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// SetVideoUploaded provides a mock function with given fields: _a0, _a1
func (_m *Store) SetVideoUploaded(_a0 context.Context, _a1 store.SetVideoUploadedParams) (store.MediaSet, error) {
ret := _m.Called(_a0, _a1)
var r0 store.MediaSet
if rf, ok := ret.Get(0).(func(context.Context, store.SetVideoUploadedParams) store.MediaSet); ok {
r0 = rf(_a0, _a1)
} else {
r0 = ret.Get(0).(store.MediaSet)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, store.SetVideoUploadedParams) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}

View File

@ -0,0 +1,70 @@
// Code generated by mockery v2.9.4. DO NOT EDIT.
package mocks
import (
context "context"
io "io"
mock "github.com/stretchr/testify/mock"
youtube "github.com/kkdai/youtube/v2"
)
// YoutubeClient is an autogenerated mock type for the YoutubeClient type
type YoutubeClient struct {
mock.Mock
}
// GetStreamContext provides a mock function with given fields: _a0, _a1, _a2
func (_m *YoutubeClient) GetStreamContext(_a0 context.Context, _a1 *youtube.Video, _a2 *youtube.Format) (io.ReadCloser, int64, error) {
ret := _m.Called(_a0, _a1, _a2)
var r0 io.ReadCloser
if rf, ok := ret.Get(0).(func(context.Context, *youtube.Video, *youtube.Format) io.ReadCloser); ok {
r0 = rf(_a0, _a1, _a2)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(io.ReadCloser)
}
}
var r1 int64
if rf, ok := ret.Get(1).(func(context.Context, *youtube.Video, *youtube.Format) int64); ok {
r1 = rf(_a0, _a1, _a2)
} else {
r1 = ret.Get(1).(int64)
}
var r2 error
if rf, ok := ret.Get(2).(func(context.Context, *youtube.Video, *youtube.Format) error); ok {
r2 = rf(_a0, _a1, _a2)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// GetVideoContext provides a mock function with given fields: _a0, _a1
func (_m *YoutubeClient) GetVideoContext(_a0 context.Context, _a1 string) (*youtube.Video, error) {
ret := _m.Called(_a0, _a1)
var r0 *youtube.Video
if rf, ok := ret.Get(0).(func(context.Context, string) *youtube.Video); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*youtube.Video)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}

View File

@ -48,6 +48,7 @@ require (
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rs/cors v1.8.0 // indirect
github.com/stretchr/objx v0.2.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.7.0 // indirect
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect

View File

@ -584,6 +584,7 @@ github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=

View File

@ -1,13 +1,125 @@
package media_test
import (
"context"
"fmt"
"io"
"os"
"testing"
"git.netflux.io/rob/clipper/config"
"git.netflux.io/rob/clipper/generated/mocks"
"git.netflux.io/rob/clipper/generated/store"
"git.netflux.io/rob/clipper/media"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
)
func TestGetAudioSegment(t *testing.T) {
testCases := []struct {
name string
fixturePath string
fixtureLen int64
startFrame, endFrame int64
channels int32
numBins int
wantPeaks []int16
wantErr string
}{
{
name: "entire fixture, stereo, 1 bin",
fixturePath: "testdata/tone-44100-stereo-int16.raw",
fixtureLen: 176400,
startFrame: 0,
endFrame: 44100,
channels: 2,
numBins: 1,
wantPeaks: []int16{32747, 32747},
},
{
name: "entire fixture, stereo, 4 bins",
fixturePath: "testdata/tone-44100-stereo-int16.raw",
fixtureLen: 176400,
startFrame: 0,
endFrame: 44100,
channels: 2,
numBins: 4,
wantPeaks: []int16{8173, 8177, 16366, 16370, 24557, 24555, 32747, 32747},
},
{
name: "entire fixture, stereo, 16 bins",
fixturePath: "testdata/tone-44100-stereo-int16.raw",
fixtureLen: 176400,
startFrame: 0,
endFrame: 44100,
channels: 2,
numBins: 16,
wantPeaks: []int16{2029, 2029, 4075, 4076, 6124, 6125, 8173, 8177, 10222, 10221, 12267, 12265, 14314, 14313, 16366, 16370, 18413, 18411, 20453, 20454, 22505, 22508, 24557, 24555, 26604, 26605, 28644, 28643, 30698, 30694, 32747, 32747},
},
{
name: "entire fixture, mono, 1 bin",
fixturePath: "testdata/tone-44100-mono-int16.raw",
fixtureLen: 88200,
startFrame: 0,
endFrame: 44100,
channels: 1,
numBins: 1,
wantPeaks: []int16{32748},
},
{
name: "entire fixture, mono, 32 bins",
fixturePath: "testdata/tone-44100-mono-int16.raw",
fixtureLen: 88200,
startFrame: 0,
endFrame: 44100,
channels: 1,
numBins: 32,
wantPeaks: []int16{1026, 2030, 3071, 4075, 5122, 6126, 7167, 8172, 9213, 10217, 11259, 12264, 13311, 14315, 15360, 16364, 17405, 18412, 19450, 20453, 21497, 22504, 23549, 24554, 25599, 26607, 27641, 28642, 29688, 30738, 31746, 32748},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
expectedBytes := (tc.endFrame - tc.startFrame) * int64(tc.channels) * media.SizeOfInt16
audioFile, err := os.Open(tc.fixturePath)
require.NoError(t, err)
defer audioFile.Close()
audioData := io.NopCloser(io.LimitReader(audioFile, int64(expectedBytes)))
mediaSetID := uuid.New()
mediaSet := store.MediaSet{ID: mediaSetID, AudioChannels: tc.channels}
// store is passed the mediaSetID and returns a mediaSet
store := &mocks.Store{}
store.On("GetMediaSet", mock.Anything, mediaSetID).Return(mediaSet, nil)
defer store.AssertExpectations(t)
// S3 is passed the expected byte range, and returns an io.Reader
s3Client := &mocks.S3Client{}
s3Client.On("GetObject", mock.Anything, mock.MatchedBy(func(input *s3.GetObjectInput) bool {
return *input.Range == fmt.Sprintf("bytes=0-%d", expectedBytes)
})).Return(&s3.GetObjectOutput{Body: audioData, ContentLength: tc.fixtureLen}, nil)
defer s3Client.AssertExpectations(t)
s3API := media.S3API{S3Client: s3Client, S3PresignClient: &mocks.S3PresignClient{}}
service := media.NewMediaSetService(store, nil, s3API, config.Config{}, zap.NewNop())
peaks, err := service.GetAudioSegment(context.Background(), mediaSetID, tc.startFrame, tc.endFrame, tc.numBins)
if tc.wantErr == "" {
assert.NoError(t, err)
assert.Equal(t, tc.wantPeaks, peaks)
} else {
assert.EqualError(t, err, tc.wantErr)
}
})
}
}
type testReader struct {
count int
data [][]byte

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

View File

@ -1,5 +1,9 @@
package media
//go:generate mockery --recursive --name S3* --output ../generated/mocks
//go:generate mockery --recursive --name Store --output ../generated/mocks
//go:generate mockery --recursive --name YoutubeClient --output ../generated/mocks
import (
"context"
"io"