2022-03-03 00:53:26 -06:00
package filestorage
import (
"context"
"fmt"
"reflect"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
type cmdErrorOutput struct {
message string
args [ ] interface { }
instance error
}
type cmdDelete struct {
path string
error * cmdErrorOutput
}
type cmdUpsert struct {
cmd UpsertFileCommand
error * cmdErrorOutput
}
type cmdCreateFolder struct {
path string
error * cmdErrorOutput
}
type cmdDeleteFolder struct {
path string
error * cmdErrorOutput
}
type queryGetInput struct {
path string
}
type fileNameCheck struct {
v string
}
type filePropertiesCheck struct {
v map [ string ] string
}
type fileContentsCheck struct {
v [ ] byte
}
type fileSizeCheck struct {
v int64
}
type fileMimeTypeCheck struct {
v string
}
type filePathCheck struct {
v string
}
type listSizeCheck struct {
v int
}
type listHasMoreCheck struct {
v bool
}
type listLastPathCheck struct {
v string
}
func fContents ( contents [ ] byte ) interface { } {
return fileContentsCheck { v : contents }
}
func fName ( name string ) interface { } {
return fileNameCheck { v : name }
}
func fPath ( path string ) interface { } {
return filePathCheck { v : path }
}
func fProperties ( properties map [ string ] string ) interface { } {
return filePropertiesCheck { v : properties }
}
func fSize ( size int64 ) interface { } {
return fileSizeCheck { v : size }
}
func fMimeType ( mimeType string ) interface { } {
return fileMimeTypeCheck { v : mimeType }
}
func listSize ( size int ) interface { } {
return listSizeCheck { v : size }
}
func listHasMore ( hasMore bool ) interface { } {
return listHasMoreCheck { v : hasMore }
}
func listLastPath ( path string ) interface { } {
return listLastPathCheck { v : path }
}
func checks ( c ... interface { } ) [ ] interface { } {
return c
}
type queryGet struct {
input queryGetInput
checks [ ] interface { }
}
type queryListFilesInput struct {
path string
paging * Paging
options * ListOptions
}
type queryListFiles struct {
input queryListFilesInput
list [ ] interface { }
files [ ] [ ] interface { }
}
type queryListFoldersInput struct {
path string
options * ListOptions
}
type queryListFolders struct {
input queryListFoldersInput
checks [ ] [ ] interface { }
}
func interfaceName ( myvar interface { } ) string {
if t := reflect . TypeOf ( myvar ) ; t . Kind ( ) == reflect . Ptr {
return "*" + t . Elem ( ) . Name ( )
} else {
return t . Name ( )
}
}
func handleCommand ( t * testing . T , ctx context . Context , cmd interface { } , cmdName string , fs FileStorage ) {
t . Helper ( )
var err error
var expectedErr * cmdErrorOutput
switch c := cmd . ( type ) {
case cmdDelete :
err = fs . Delete ( ctx , c . path )
if c . error == nil {
require . NoError ( t , err , "%s: should be able to delete %s" , cmdName , c . path )
}
expectedErr = c . error
case cmdUpsert :
err = fs . Upsert ( ctx , & c . cmd )
if c . error == nil {
require . NoError ( t , err , "%s: should be able to upsert file %s" , cmdName , c . cmd . Path )
}
expectedErr = c . error
case cmdCreateFolder :
err = fs . CreateFolder ( ctx , c . path )
if c . error == nil {
require . NoError ( t , err , "%s: should be able to create folder %s" , cmdName , c . path )
}
expectedErr = c . error
case cmdDeleteFolder :
err = fs . DeleteFolder ( ctx , c . path )
if c . error == nil {
require . NoError ( t , err , "%s: should be able to delete %s" , cmdName , c . path )
}
expectedErr = c . error
default :
t . Fatalf ( "unrecognized command %s" , cmdName )
}
if expectedErr != nil && err != nil {
if expectedErr . instance != nil {
require . ErrorIs ( t , err , expectedErr . instance )
}
if expectedErr . message != "" {
require . Errorf ( t , err , expectedErr . message , expectedErr . args ... )
}
}
}
func runChecks ( t * testing . T , stepName string , path string , output interface { } , checks [ ] interface { } ) {
2022-06-01 13:55:22 -05:00
if len ( checks ) == 0 {
2022-03-03 00:53:26 -06:00
return
}
runFileMetadataCheck := func ( file FileMetadata , check interface { } , checkName string ) {
switch c := check . ( type ) {
case filePropertiesCheck :
require . Equal ( t , c . v , file . Properties , "%s-%s %s" , stepName , checkName , path )
case fileNameCheck :
require . Equal ( t , c . v , file . Name , "%s-%s %s" , stepName , checkName , path )
case fileSizeCheck :
require . Equal ( t , c . v , file . Size , "%s-%s %s" , stepName , checkName , path )
case fileMimeTypeCheck :
require . Equal ( t , c . v , file . MimeType , "%s-%s %s" , stepName , checkName , path )
case filePathCheck :
require . Equal ( t , c . v , file . FullPath , "%s-%s %s" , stepName , checkName , path )
default :
t . Fatalf ( "unrecognized file check %s" , checkName )
}
}
switch o := output . ( type ) {
2022-03-15 12:21:22 -05:00
case * File :
2022-03-03 00:53:26 -06:00
for _ , check := range checks {
checkName := interfaceName ( check )
if fileContentsCheck , ok := check . ( fileContentsCheck ) ; ok {
require . Equal ( t , fileContentsCheck . v , o . Contents , "%s-%s %s" , stepName , checkName , path )
} else {
runFileMetadataCheck ( o . FileMetadata , check , checkName )
}
}
case FileMetadata :
for _ , check := range checks {
runFileMetadataCheck ( o , check , interfaceName ( check ) )
}
2022-05-16 12:26:40 -05:00
case * ListResponse :
2022-03-03 00:53:26 -06:00
for _ , check := range checks {
c := check
checkName := interfaceName ( c )
switch c := check . ( type ) {
case listSizeCheck :
2022-05-16 12:26:40 -05:00
require . Equal ( t , c . v , len ( o . Files ) , "%s %s\nReceived %s" , stepName , path , o )
2022-03-03 00:53:26 -06:00
case listHasMoreCheck :
2022-05-16 12:26:40 -05:00
require . Equal ( t , c . v , o . HasMore , "%s %s\nReceived %s" , stepName , path , o )
2022-03-03 00:53:26 -06:00
case listLastPathCheck :
2022-05-16 12:26:40 -05:00
require . Equal ( t , c . v , o . LastPath , "%s %s\nReceived %s" , stepName , path , o )
2022-03-03 00:53:26 -06:00
default :
t . Fatalf ( "unrecognized list check %s" , checkName )
}
}
2022-03-15 12:21:22 -05:00
2022-03-03 00:53:26 -06:00
default :
t . Fatalf ( "unrecognized output %s" , interfaceName ( output ) )
}
}
2022-03-15 12:21:22 -05:00
func formatPathStructure ( files [ ] * File ) string {
2022-03-03 00:53:26 -06:00
if len ( files ) == 0 {
return "<<EMPTY>>"
}
res := "\n"
for _ , f := range files {
res = fmt . Sprintf ( "%s%s\n" , res , f . FullPath )
}
return res
}
func handleQuery ( t * testing . T , ctx context . Context , query interface { } , queryName string , fs FileStorage ) {
t . Helper ( )
switch q := query . ( type ) {
case queryGet :
inputPath := q . input . path
file , err := fs . Get ( ctx , inputPath )
require . NoError ( t , err , "%s: should be able to get file %s" , queryName , inputPath )
if q . checks != nil && len ( q . checks ) > 0 {
require . NotNil ( t , file , "%s %s" , queryName , inputPath )
require . Equal ( t , strings . ToLower ( inputPath ) , strings . ToLower ( file . FullPath ) , "%s %s" , queryName , inputPath )
2022-03-15 12:21:22 -05:00
runChecks ( t , queryName , inputPath , file , q . checks )
2022-03-03 00:53:26 -06:00
} else {
require . Nil ( t , file , "%s %s" , queryName , inputPath )
}
case queryListFiles :
inputPath := q . input . path
2022-03-15 12:21:22 -05:00
resp , err := fs . List ( ctx , inputPath , q . input . paging , q . input . options )
2022-03-03 00:53:26 -06:00
require . NoError ( t , err , "%s: should be able to list files in %s" , queryName , inputPath )
require . NotNil ( t , resp )
if q . list != nil && len ( q . list ) > 0 {
2022-05-16 12:26:40 -05:00
runChecks ( t , queryName , inputPath , resp , q . list )
2022-03-03 00:53:26 -06:00
} else {
require . NotNil ( t , resp , "%s %s" , queryName , inputPath )
require . Equal ( t , false , resp . HasMore , "%s %s" , queryName , inputPath )
require . Equal ( t , 0 , len ( resp . Files ) , "%s %s" , queryName , inputPath )
require . Equal ( t , "" , resp . LastPath , "%s %s" , queryName , inputPath )
}
if q . files != nil {
require . Equal ( t , len ( resp . Files ) , len ( q . files ) , "%s expected a check for each actual file at path: \"%s\". actual: %s" , queryName , inputPath , formatPathStructure ( resp . Files ) )
for i , file := range resp . Files {
runChecks ( t , queryName , inputPath , file , q . files [ i ] )
}
}
case queryListFolders :
inputPath := q . input . path
2022-03-15 12:21:22 -05:00
opts := q . input . options
if opts == nil {
opts = & ListOptions {
Recursive : true ,
WithFiles : false ,
WithFolders : true ,
WithContents : false ,
2022-04-21 14:27:43 -05:00
Filter : nil ,
2022-03-15 12:21:22 -05:00
}
} else {
opts . WithFolders = true
opts . WithFiles = false
}
resp , err := fs . List ( ctx , inputPath , & Paging {
After : "" ,
First : 100000 ,
} , opts )
2022-03-03 00:53:26 -06:00
require . NotNil ( t , resp )
require . NoError ( t , err , "%s: should be able to list folders in %s" , queryName , inputPath )
if q . checks != nil {
2022-03-15 12:21:22 -05:00
require . Equal ( t , len ( resp . Files ) , len ( q . checks ) , "%s: expected a check for each actual folder at path: \"%s\". actual: %s" , queryName , inputPath , formatPathStructure ( resp . Files ) )
for i , file := range resp . Files {
2022-03-03 00:53:26 -06:00
runChecks ( t , queryName , inputPath , file , q . checks [ i ] )
}
} else {
2022-03-15 12:21:22 -05:00
require . Equal ( t , 0 , len ( resp . Files ) , "%s %s" , queryName , inputPath )
2022-03-03 00:53:26 -06:00
}
default :
t . Fatalf ( "unrecognized query %s" , queryName )
}
}
func executeTestStep ( t * testing . T , ctx context . Context , step interface { } , stepNumber int , fs FileStorage ) {
name := fmt . Sprintf ( "[%d]%s" , stepNumber , interfaceName ( step ) )
switch s := step . ( type ) {
case queryGet :
handleQuery ( t , ctx , s , name , fs )
case queryListFiles :
handleQuery ( t , ctx , s , name , fs )
case queryListFolders :
handleQuery ( t , ctx , s , name , fs )
case cmdUpsert :
handleCommand ( t , ctx , s , name , fs )
case cmdDelete :
handleCommand ( t , ctx , s , name , fs )
case cmdCreateFolder :
handleCommand ( t , ctx , s , name , fs )
case cmdDeleteFolder :
handleCommand ( t , ctx , s , name , fs )
default :
t . Fatalf ( "unrecognized step %s" , name )
}
}