Skip to content

Commit

Permalink
add: write table driven test cases (#3)
Browse files Browse the repository at this point in the history
### What kind of change does this PR introduce?
In this updated version, i try to write table test driven function. Why
this method? because it's very simple and easy to understand. This
updated version includes:

- Test cases
- ### Functional updates:
- `RemoveUploadedFile` function is updated to accept two string
parameter instead of `*RFileRequest` reference, because i think it's
unnecessary and hard to understand why function take reference of
struct. Also `uniqueName` function is updated to take `string` parameter
instead of `*RFileRequest` reference.
  • Loading branch information
Nicolas-ggd authored Dec 16, 2024
2 parents 2ffb4cb + 81c72ca commit 6463e12
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 15 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ vendor/
*.cache
*.bak
*.old
/test

# Build directories
/dist/
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,10 @@ func Upload(c *gin.Context) {
if isLast {
// You can perform your own logic here, before return 200 status,
// it's better to remove chunks which is uploaded and doesn't use anymore
fstream.RemoveUploadedFile(&fileReq)
err = fstream.RemoveUploadedFile("/dir", "filename.jpeg")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err})
}
}

c.JSON(http.StatusOK, gin.H{"message": "file chunk processed"})
Expand Down
26 changes: 16 additions & 10 deletions file.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ type RFileRequest struct {
}

// uniqueName function generates unique string using UUID
func uniqueName(request *RFileRequest) string {
ext := filepath.Ext(request.UploadFile.Filename)
func uniqueName(fileName string) string {
ext := filepath.Ext(fileName)

id, err := uuid.NewUUID()
if err != nil {
Expand All @@ -55,20 +55,26 @@ func uniqueName(request *RFileRequest) string {
return fmt.Sprintf("%s%s", id.String(), ext)
}

// RemoveUploadedFile function removes uploaded file from uploaded directory, it takes param and returns nothing:
// RemoveUploadedFile function removes uploaded file from uploaded directory and returns error if something went wrong:
//
// Takes:
//
// - RFileRequest struct
// - uploadDir (string) - upload directory where file lives
// - fileName (string) - file name
//
// Returns:
// - error if something went wrong, in this case if file doesn't removed function returns error
//
// Use this function in your handler after file is uploaded
func RemoveUploadedFile(r *RFileRequest) {
filePath := filepath.Join(r.UploadDirectory, r.UploadFile.Filename)
func RemoveUploadedFile(uploadDir, fileName string) error {
filePath := filepath.Join(uploadDir, fileName)

e := os.Remove(filePath)
if e != nil {
log.Printf("error removing file: %v", e)
err := os.Remove(filePath)
if err != nil {
return err
}

return nil
}

// prettyByteSize function is used to concrete the file size
Expand Down Expand Up @@ -134,7 +140,7 @@ func StoreChunk(r *RFileRequest) (*File, error) {

// Check if FileUniqueName field is true to generate unique name for file
if r.FileUniqueName {
uName := uniqueName(r)
uName := uniqueName(r.UploadFile.Filename)
rFile.FileUniqueName = &uName
}

Expand Down
182 changes: 178 additions & 4 deletions file_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,182 @@
package fstream

import "testing"
import (
"fmt"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"mime/multipart"
"os"
"path/filepath"
"testing"
)

func FuzzStoreChunk(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
})
func TestIsAllowedExtension(t *testing.T) {
testCases := []struct {
fileExtension []string
fileName string
expected bool
expectedName string
}{
{
fileExtension: []string{".jpeg", ".jpg"},
fileName: "test.jpeg",
expected: true,
expectedName: "Success",
},
{
fileExtension: []string{".png", ".webp"},
fileName: "test.jpg",
expected: false,
expectedName: "Failed",
},
}

for _, tc := range testCases {
t.Run(tc.expectedName, func(t *testing.T) {
res := IsAllowExtension(tc.fileExtension, tc.fileName)
assert.Equal(t, tc.expected, res)
})
}
}

func TestRemoveUploadedFile(t *testing.T) {
// create test directory
err := os.MkdirAll("test", 0777)
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll("test")

// simulate file upload
testFileName := "testfile.txt"
testFilePath := filepath.Join("test", testFileName)
_, err = os.Create(testFilePath)
if err != nil {
t.Fatal(err)
}

testCases := []struct {
uploadDir string
fileName string
expected error
expectedName string
}{
{
uploadDir: "test",
fileName: testFileName,
expected: nil,
expectedName: "Success",
},
}

for _, tc := range testCases {
t.Run(tc.expectedName, func(t *testing.T) {
err = RemoveUploadedFile(tc.uploadDir, tc.fileName)
assert.Equal(t, tc.expected, err)

if _, statErr := os.Stat(testFilePath); !os.IsNotExist(statErr) {
t.Errorf("File %s was not removed", testFilePath)
}
})
}
}

func TestStoreChunk(t *testing.T) {
testCases := []struct {
name string
fileContent []byte
maxRange int
fileUniqueName bool
expectError bool
expectedFileSize int
}{
{
name: "Successful file upload with unique name",
fileContent: []byte("This is a test chunk"),
maxRange: 19, // Full file uploaded
fileUniqueName: true,
expectError: false,
expectedFileSize: 19,
},
{
name: "Successful file upload without unique name",
fileContent: []byte("Another test chunk"),
maxRange: 20, // Full file uploaded
fileUniqueName: false,
expectError: false,
expectedFileSize: 20,
},
{
name: "Partial upload",
fileContent: []byte("Partial chunk"),
maxRange: 7, // Partial file uploaded
fileUniqueName: false,
expectError: false,
expectedFileSize: 0, // File should not be finalized
},
{
name: "Error due to maxRange exceeding file size",
fileContent: []byte("Invalid max range"),
maxRange: 50, // maxRange > FileSize
fileUniqueName: false,
expectError: true,
expectedFileSize: 0,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
file, err := os.CreateTemp("", "")
if err != nil {
assert.Error(t, err)
}
defer os.Remove(file.Name())

_, err = file.Write(tc.fileContent)
if err != nil {
assert.Error(t, err)
}
defer file.Close()

multipartFile, err := os.Open(file.Name())
if err != nil {
assert.Error(t, err)
}
defer multipartFile.Close()

fileHeader := &multipart.FileHeader{
Filename: filepath.Base(file.Name()),
Size: int64(len(tc.fileContent)),
}

r := &RFileRequest{
File: multipartFile,
UploadFile: fileHeader,
MaxRange: tc.maxRange,
FileSize: len(tc.fileContent),
UploadDirectory: t.TempDir(), // Temporary directory for test
FileUniqueName: tc.fileUniqueName,
}

resFile, err := StoreChunk(r)
if err != nil {
assert.Error(t, err)
}

expectedFilePath := filepath.Join(r.UploadDirectory + fileHeader.Filename)
fmt.Printf("Expected Path: %s\n", expectedFilePath)

assert.FileExists(t, expectedFilePath)

actualContent, err := os.ReadFile(expectedFilePath)
require.NoError(t, err)
assert.Equal(t, tc.fileContent, actualContent)

if resFile != nil {
assert.Equal(t, fileHeader.Filename, resFile.FileName)
assert.Equal(t, expectedFilePath, resFile.FilePath)
assert.Equal(t, filepath.Ext(fileHeader.Filename), resFile.FileExtension)
}
})
}
}
7 changes: 7 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,10 @@ module github.com/Nicolas-ggd/filestream
go 1.23.0

require github.com/google/uuid v1.6.0

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.10.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
9 changes: 9 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,2 +1,11 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 comments on commit 6463e12

Please sign in to comment.