Skip to content

Commit

Permalink
(feat): Add service.FileService test coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
lasith-kg committed Dec 28, 2023
1 parent 8b759f2 commit a3e1457
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 62 deletions.
81 changes: 40 additions & 41 deletions coverage.html
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@

<option value="file9">github.com/reecetech/ebs-bootstrap/internal/service/device.go (100.0%)</option>

<option value="file10">github.com/reecetech/ebs-bootstrap/internal/service/file.go (0.0%)</option>
<option value="file10">github.com/reecetech/ebs-bootstrap/internal/service/file.go (100.0%)</option>

<option value="file11">github.com/reecetech/ebs-bootstrap/internal/service/filesystem.go (0.0%)</option>

Expand Down Expand Up @@ -232,13 +232,13 @@

type ChangeOwnerAction struct {
path string
uid int
gid int
uid model.UserId
gid model.GroupId
mode model.Mode
fileService service.FileService
}

func NewChangeOwnerAction(p string, uid int, gid int, fs service.FileService) *ChangeOwnerAction <span class="cov0" title="0">{
func NewChangeOwnerAction(p string, uid model.UserId, gid model.GroupId, fs service.FileService) *ChangeOwnerAction <span class="cov0" title="0">{
return &amp;ChangeOwnerAction{
path: p,
uid: uid,
Expand Down Expand Up @@ -1126,7 +1126,6 @@
<pre class="file" id="file10" style="display: none">package service

import (
"fmt"
"os"
"syscall"

Expand All @@ -1142,55 +1141,55 @@
type FileService interface {
GetFile(file string) (*model.File, error)
CreateDirectory(path string) error
ChangeOwner(file string, uid int, gid int) error
ChangeOwner(file string, uid model.UserId, gid model.GroupId) error
ChangePermissions(file string, perms model.FilePermissions) error
}

// File Service Interface [END]

type UnixFileService struct{}

func NewUnixFileService() *UnixFileService <span class="cov0" title="0">{
func NewUnixFileService() *UnixFileService <span class="cov8" title="1">{
return &amp;UnixFileService{}
}</span>

func (ufs *UnixFileService) GetFile(file string) (*model.File, error) <span class="cov0" title="0">{
func (ufs *UnixFileService) GetFile(file string) (*model.File, error) <span class="cov8" title="1">{
info, err := os.Lstat(file)
if err != nil </span><span class="cov0" title="0">{
if err != nil </span><span class="cov8" title="1">{
return nil, err
}</span>
<span class="cov0" title="0">if stat, ok := info.Sys().(*syscall.Stat_t); ok </span><span class="cov0" title="0">{
var ft model.FileType
switch mode := info.Mode(); </span>{
case mode.IsRegular():<span class="cov0" title="0">
ft = model.RegularFile</span>
case mode.IsDir():<span class="cov0" title="0">
ft = model.Directory</span>
default:<span class="cov0" title="0">
ft = model.Special</span>
}
<span class="cov0" title="0">return &amp;model.File{
Path: file,
DeviceId: int(stat.Dev),
InodeNo: int(stat.Ino),
Uid: int(stat.Uid),
Gid: int(stat.Gid),
Permissions: model.FilePermissions(info.Mode().Perm()),
Type: ft,
}, nil</span>
<span class="cov8" title="1">stat := info.Sys().(*syscall.Stat_t)

var ft model.FileType
switch mode := info.Mode(); </span>{
case mode.IsRegular():<span class="cov8" title="1">
ft = model.RegularFile</span>
case mode.IsDir():<span class="cov8" title="1">
ft = model.Directory</span>
default:<span class="cov8" title="1">
ft = model.Special</span>
}
<span class="cov0" title="0">return nil, fmt.Errorf("🔴 %s: Failed to get stats", file)</span>

<span class="cov8" title="1">return &amp;model.File{
Path: file,
DeviceId: stat.Dev,
InodeNo: stat.Ino,
UserId: model.UserId(stat.Uid),
GroupId: model.GroupId(stat.Gid),
Permissions: model.FilePermissions(info.Mode().Perm()),
Type: ft,
}, nil</span>
}

func (ufs *UnixFileService) CreateDirectory(path string) error <span class="cov0" title="0">{
func (ufs *UnixFileService) CreateDirectory(path string) error <span class="cov8" title="1">{
return os.MkdirAll(path, DefaultDirectoryPermissions)
}</span>

func (ufs *UnixFileService) ChangeOwner(file string, uid int, gid int) error <span class="cov0" title="0">{
return os.Chown(file, uid, gid)
func (ufs *UnixFileService) ChangeOwner(file string, uid model.UserId, gid model.GroupId) error <span class="cov8" title="1">{
return os.Chown(file, int(uid), int(gid))
}</span>

func (ufs *UnixFileService) ChangePermissions(file string, perms model.FilePermissions) error <span class="cov0" title="0">{
func (ufs *UnixFileService) ChangePermissions(file string, perms model.FilePermissions) error <span class="cov8" title="1">{
return os.Chmod(file, perms.Perm())
}</span>
</pre>
Expand Down Expand Up @@ -1758,13 +1757,13 @@
if err != nil </span><span class="cov0" title="0">{
return nil, fmt.Errorf("🔴 Failed to get current user")
}</span>
<span class="cov0" title="0">uid, err := strconv.Atoi(u.Uid)
<span class="cov0" title="0">uid, err := strconv.ParseUint(u.Uid, 10, 32)
if err != nil </span><span class="cov0" title="0">{
return nil, fmt.Errorf("🔴 Failed to cast user (id) to integer")
return nil, fmt.Errorf("🔴 Failed to cast user (id) to unsigned 32-bit integer")
}</span>
<span class="cov0" title="0">return &amp;model.User{
Name: u.Name,
Uid: uid,
Id: model.UserId(uid),
}, nil</span>
}

Expand All @@ -1783,12 +1782,12 @@
}</span>
}

<span class="cov0" title="0">uid, err := strconv.Atoi(u.Uid)
<span class="cov0" title="0">uid, err := strconv.ParseUint(u.Uid, 10, 32)
if err != nil </span><span class="cov0" title="0">{
return nil, fmt.Errorf("🔴 Failed to cast user (id=%s) to integer", u.Uid)
return nil, fmt.Errorf("🔴 Failed to cast user (id=%s) to unsigned 32-bit integer", u.Uid)
}</span>

<span class="cov0" title="0">return &amp;model.User{Name: u.Username, Uid: uid}, nil</span>
<span class="cov0" title="0">return &amp;model.User{Name: u.Username, Id: model.UserId(uid)}, nil</span>
}

func (uos *UnixOwnerService) GetGroup(grp string) (*model.Group, error) <span class="cov0" title="0">{
Expand All @@ -1806,12 +1805,12 @@
}</span>
}

<span class="cov0" title="0">gid, err := strconv.Atoi(g.Gid)
<span class="cov0" title="0">gid, err := strconv.ParseUint(g.Gid, 10, 32)
if err != nil </span><span class="cov0" title="0">{
return nil, fmt.Errorf("🔴 Failed to cast group (id=%s) to integer", g.Gid)
}</span>

<span class="cov0" title="0">return &amp;model.Group{Name: g.Name, Gid: gid}, nil</span>
<span class="cov0" title="0">return &amp;model.Group{Name: g.Name, Id: model.GroupId(gid)}, nil</span>
}
</pre>

Expand Down
41 changes: 20 additions & 21 deletions internal/service/file.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package service

import (
"fmt"
"os"
"syscall"

Expand Down Expand Up @@ -34,27 +33,27 @@ func (ufs *UnixFileService) GetFile(file string) (*model.File, error) {
if err != nil {
return nil, err
}
if stat, ok := info.Sys().(*syscall.Stat_t); ok {
var ft model.FileType
switch mode := info.Mode(); {
case mode.IsRegular():
ft = model.RegularFile
case mode.IsDir():
ft = model.Directory
default:
ft = model.Special
}
return &model.File{
Path: file,
DeviceId: stat.Dev,
InodeNo: stat.Ino,
UserId: model.UserId(stat.Uid),
GroupId: model.GroupId(stat.Gid),
Permissions: model.FilePermissions(info.Mode().Perm()),
Type: ft,
}, nil
stat := info.Sys().(*syscall.Stat_t)

var ft model.FileType
switch mode := info.Mode(); {
case mode.IsRegular():
ft = model.RegularFile
case mode.IsDir():
ft = model.Directory
default:
ft = model.Special
}
return nil, fmt.Errorf("🔴 %s: Failed to get stats", file)

return &model.File{
Path: file,
DeviceId: stat.Dev,
InodeNo: stat.Ino,
UserId: model.UserId(stat.Uid),
GroupId: model.GroupId(stat.Gid),
Permissions: model.FilePermissions(info.Mode().Perm()),
Type: ft,
}, nil
}

func (ufs *UnixFileService) CreateDirectory(path string) error {
Expand Down
127 changes: 127 additions & 0 deletions internal/service/file_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package service

import (
"os"
"path"
"syscall"
"testing"

"github.com/reecetech/ebs-bootstrap/internal/model"
"github.com/reecetech/ebs-bootstrap/internal/utils"
)

func TestGetFile(t *testing.T) {
file, err := regularFile()
defer os.Remove(file.Path)
utils.CheckError("temporaryFile()", t, nil, err)

dir, err := directory()
defer os.RemoveAll(dir.Path)
utils.CheckError("temporaryDirectory()", t, nil, err)

spec, err := special()
utils.CheckError("lstat()", t, nil, err)

ufs := NewUnixFileService()

subtests := []struct {
Name string
Path string
ExpectedOutput *model.File
ShouldExpectErr bool
}{
{
Name: "Regular File",
Path: file.Path,
ExpectedOutput: file,
ShouldExpectErr: false,
},
{
Name: "Directory",
Path: dir.Path,
ExpectedOutput: dir,
ShouldExpectErr: false,
},
{
Name: "Special",
Path: spec.Path,
ExpectedOutput: spec,
ShouldExpectErr: false,
},
{
Name: "Non-Existing File",
Path: "/dev/does-not-exist",
ExpectedOutput: nil,
ShouldExpectErr: true,
},
}
for _, subtest := range subtests {
output, err := ufs.GetFile(subtest.Path)
utils.ExpectErr("ufs.GetFile()", t, subtest.ShouldExpectErr, err)
utils.CheckOutput("ufs.GetFile()", t, subtest.ExpectedOutput, output)
}
}

func TestDirectoryModifications(t *testing.T) {
ufs := NewUnixFileService()

// Create Temporary Directory
dir, err := directory()
utils.ExpectErr("temporaryDirectory()", t, false, err)
defer os.RemoveAll(dir.Path)

// Create Nested Directory Inside Temporary Directory
nestedDir := path.Join(dir.Path, "nested")
err = ufs.CreateDirectory(nestedDir)
utils.ExpectErr("ufs.CreateDirectory()", t, false, err)

// Change Owner of Nested Directory to Match Temporary Directory
err = ufs.ChangeOwner(nestedDir, dir.UserId, dir.GroupId)
utils.ExpectErr("ufs.ChangeOwner()", t, false, err)

// Change Permissions of Nested Directory to Match Temporary Directory
err = ufs.ChangePermissions(nestedDir, dir.Permissions)
utils.ExpectErr("ufs.ChangePermissions()", t, false, err)
}

// Create a temporary file
func regularFile() (*model.File, error) {
file, err := os.CreateTemp("", "temp_file")
if err != nil {
return nil, err
}
defer file.Close()
return lstat(file.Name(), model.RegularFile)
}

// Create a temporary directory
func directory() (*model.File, error) {
dir, err := os.MkdirTemp("", "temp_dir")
if err != nil {
return nil, err
}
return lstat(dir, model.Directory)
}

// Return lstat of /dev/null which is classified as a 'special' file in Unix
func special() (*model.File, error) {
return lstat("/dev/null", model.Special)
}

func lstat(path string, ft model.FileType) (*model.File, error) {
info, err := os.Lstat(path)
if err != nil {
return nil, err
}
stat := info.Sys().(*syscall.Stat_t)

return &model.File{
Path: path,
Type: ft,
DeviceId: stat.Dev,
InodeNo: stat.Ino,
UserId: model.UserId(stat.Uid),
GroupId: model.GroupId(stat.Gid),
Permissions: model.FilePermissions(info.Mode().Perm()),
}, nil
}
9 changes: 9 additions & 0 deletions internal/utils/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ func CheckOutput(id string, t *testing.T, expected interface{}, actual interface
}
}

func ExpectErr(id string, t *testing.T, shouldExpectErr bool, err error) {
if shouldExpectErr && err == nil {
t.Fatalf("%s [error] mismatch: No error detected, despite it being expected", id)
}
if !shouldExpectErr && err != nil {
t.Fatalf("%s [error] mismatch: Expected=%v, Actual=%s", id, nil, err)
}
}

func CheckError(id string, t *testing.T, expected error, actual error) {
if actual != nil {
if expected == nil {
Expand Down

0 comments on commit a3e1457

Please sign in to comment.