asteris-llc/converge

View on GitHub
resource/user/user_test.go

Summary

Maintainability
F
1 wk
Test Coverage
// Copyright © 2016 Asteris, LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package user_test

import (
    "fmt"
    "math"
    os "os/user"
    "runtime"
    "strconv"
    "strings"
    "testing"
    "time"

    "github.com/asteris-llc/converge/helpers/fakerenderer"
    "github.com/asteris-llc/converge/resource"
    "github.com/asteris-llc/converge/resource/user"
    "github.com/fgrid/uuid"
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/mock"
    "github.com/stretchr/testify/require"
    "golang.org/x/net/context"
)

var (
    currUser          *os.User
    currUsername      string
    currUID           string
    currGroup         *os.Group
    currGroupName     string
    currGID           string
    existingGroup     *os.Group
    existingGroupName string
    existingGID       string
    existingUser      *os.User
    existingUID       string
    tempUsername      []string
    fakeUsername      string
    fakeUID           string
    tempGroupName     []string
    fakeGroupName     string
    fakeGID           string
    err               error
)

const (
    // minGID designates the smallest valid GID
    // At a minimum, 0-32676 is valid
    minGID = 0

    // maxGID designates the largest valid GID
    // At a minimum, 0-32676 is valid
    maxGID = math.MaxInt16

    // minUID designates the smallest valid UID
    // At a minimum, 0-32676 is valid
    minUID = 0

    // maxUID designates the largest valid UID
    // At a minimum, 0-32676 is valid
    maxUID = math.MaxInt16
)

func init() {
    currUser, err = os.Current()
    if err != nil {
        panic(err)
    }

    currUsername = currUser.Username
    currUID = currUser.Uid

    currGID = currUser.Gid
    currGroup, err = os.LookupGroupId(currGID)
    if err != nil {
        panic(err)
    }
    currGroupName = currGroup.Name

    fakeUID, err = setFakeUid()
    if err != nil {
        panic(err)
    }
    fakeGID, err = setFakeGid()
    if err != nil {
        panic(err)
    }

    tempUsername = strings.Split(uuid.NewV4().String(), "-")
    fakeUsername = strings.Join(tempUsername[0:], "")
    tempGroupName = strings.Split(uuid.NewV4().String(), "-")
    fakeGroupName = strings.Join(tempUsername[0:], "")

    existingGID, err = setGid()
    if err != nil {
        panic(err)
    }
    existingGroup, err = os.LookupGroupId(existingGID)
    if err != nil {
        panic(err)
    }
    existingGroupName = existingGroup.Name

    existingUID, err = setUid()
    if err != nil {
        panic(err)
    }
    existingUser, err = os.LookupId(existingUID)
    if err != nil {
        panic(err)
    }
}

// TestUserInterface tests that User is properly implemented
func TestUserInterface(t *testing.T) {
    t.Parallel()

    assert.Implements(t, (*resource.Task)(nil), new(user.User))
}

// TestCheck tests the possible cases Check handles
func TestCheck(t *testing.T) {
    t.Parallel()

    t.Run("state=present", func(t *testing.T) {
        u := user.NewUser(new(user.System))
        u.State = user.StatePresent

        t.Run("add tests", func(t *testing.T) {
            t.Run("add user", func(t *testing.T) {
                u.Username = fakeUsername
                status, err := u.Check(context.Background(), fakerenderer.New())

                if runtime.GOOS == "linux" {
                    assert.NoError(t, err)
                    assert.Equal(t, "add user", status.Messages()[0])
                    assert.Equal(t, resource.StatusWillChange, status.StatusCode())
                    assert.Equal(t, fmt.Sprintf("<%s>", string(user.StateAbsent)), status.Diffs()["username"].Original())
                    assert.Equal(t, u.Username, status.Diffs()["username"].Current())
                    assert.True(t, status.HasChanges())
                } else {
                    assert.EqualError(t, err, "user: not supported on this system")
                }
            })

            t.Run("cannot add user", func(t *testing.T) {
                u.Username = fakeUsername
                u.GroupName = fakeGroupName
                status, err := u.Check(context.Background(), fakerenderer.New())

                if runtime.GOOS == "linux" {
                    assert.EqualError(t, err, fmt.Sprintf("cannot add user %s: group %s does not exist", u.Username, u.GroupName))
                    assert.Equal(t, resource.StatusCantChange, status.StatusCode())
                    assert.True(t, status.HasChanges())
                } else {
                    assert.EqualError(t, err, "user: not supported on this system")
                }
            })
        })

        t.Run("modify tests", func(t *testing.T) {
            t.Run("no modifications", func(t *testing.T) {
                u.Username = currUsername
                u.GroupName = "" // clear this field set from previous t.Run
                status, err := u.Check(context.Background(), fakerenderer.New())

                if runtime.GOOS == "linux" {
                    assert.NoError(t, err)
                    assert.Equal(t, resource.StatusNoChange, status.StatusCode())
                    assert.False(t, status.HasChanges())
                } else {
                    assert.EqualError(t, err, "user: not supported on this system")
                }
            })

            t.Run("cannot modify user", func(t *testing.T) {
                u.Username = currUsername
                u.GroupName = fakeGroupName
                status, err := u.Check(context.Background(), fakerenderer.New())

                if runtime.GOOS == "linux" {
                    assert.EqualError(t, err, fmt.Sprintf("cannot modify user %s: group %s does not exist", u.Username, u.GroupName))
                    assert.Equal(t, resource.StatusCantChange, status.StatusCode())
                    assert.True(t, status.HasChanges())
                } else {
                    assert.EqualError(t, err, "user: not supported on this system")
                }
            })

            t.Run("modify user", func(t *testing.T) {
                u.Username = currUsername
                u.NewUsername = fakeUsername
                u.GroupName = "" // clear this field set from previous t.Run
                status, err := u.Check(context.Background(), fakerenderer.New())

                if runtime.GOOS == "linux" {
                    assert.NoError(t, err)
                    assert.Equal(t, resource.StatusWillChange, status.StatusCode())
                    assert.Equal(t, "modify user", status.Messages()[0])
                    assert.Equal(t, u.Username, status.Diffs()["username"].Original())
                    assert.Equal(t, u.NewUsername, status.Diffs()["username"].Current())
                    assert.True(t, status.HasChanges())
                } else {
                    assert.EqualError(t, err, "user: not supported on this system")
                }
            })
        })
    })

    t.Run("state=absent", func(t *testing.T) {
        u := user.NewUser(new(user.System))
        u.State = user.StateAbsent

        t.Run("no delete-user does not exist", func(t *testing.T) {
            u.Username = fakeUsername
            status, err := u.Check(context.Background(), fakerenderer.New())

            if runtime.GOOS == "linux" {
                assert.NoError(t, err)
                assert.Equal(t, resource.StatusNoChange, status.StatusCode())
                assert.False(t, status.HasChanges())
            } else {
                assert.EqualError(t, err, "user: not supported on this system")
            }
        })

        t.Run("delete user", func(t *testing.T) {
            u.Username = currUsername
            status, err := u.Check(context.Background(), fakerenderer.New())

            if runtime.GOOS == "linux" {
                assert.NoError(t, err)
                assert.Equal(t, resource.StatusWillChange, status.StatusCode())
                assert.Equal(t, u.Username, status.Diffs()["user"].Original())
                assert.Equal(t, fmt.Sprintf("<%s>", string(user.StateAbsent)), status.Diffs()["user"].Current())
                assert.True(t, status.HasChanges())
            } else {
                assert.EqualError(t, err, "user: not supported on this system")
            }
        })
    })

    t.Run("state unknown", func(t *testing.T) {
        u := user.NewUser(new(user.System))
        u.Username = currUsername
        u.UID = currUID
        u.State = "test"
        status, err := u.Check(context.Background(), fakerenderer.New())

        if runtime.GOOS == "linux" {
            assert.EqualError(t, err, fmt.Sprintf("user: unrecognized state %s", u.State))
            assert.Equal(t, resource.StatusFatal, status.StatusCode())
        } else {
            assert.EqualError(t, err, "user: not supported on this system")
        }
    })

}

// TestApply tests all possible cases Apply handles
func TestApply(t *testing.T) {
    t.Parallel()

    t.Run("state=present", func(t *testing.T) {
        t.Run("add tests", func(t *testing.T) {
            t.Run("add user", func(t *testing.T) {
                usr := &os.User{
                    Username: fakeUsername,
                }
                m := &MockSystem{}
                u := user.NewUser(m)
                u.Username = usr.Username
                u.State = user.StatePresent
                options := user.AddUserOptions{}

                m.On("Lookup", u.Username).Return(usr, os.UnknownUserError(""))
                m.On("AddUser", u.Username, &options).Return(nil)
                status, err := u.Apply(context.Background())

                m.AssertCalled(t, "AddUser", u.Username, &options)
                assert.NoError(t, err)
                assert.Equal(t, fmt.Sprintf("added user %s", u.Username), status.Messages()[0])
            })

            t.Run("add user-update default diffs", func(t *testing.T) {
                t.Run("create_home", func(t *testing.T) {
                    usr := &os.User{
                        Username: fakeUsername,
                    }

                    m := &MockSystem2{}
                    u := user.NewUser(m)
                    u.Username = usr.Username
                    u.CreateHome = true
                    u.State = user.StatePresent
                    options := user.AddUserOptions{CreateHome: u.CreateHome}
                    usrAfterAdd := &os.User{
                        Username: u.Username,
                        HomeDir:  "/tmp/test",
                    }

                    m.userBeforeAdd = true
                    m.On("Lookup", usr.Username).Return(usr, usrAfterAdd, os.UnknownUserError(""))
                    m.On("AddUser", u.Username, &options).Return(nil)
                    m.On("Lookup", usrAfterAdd.Username).Return(usr, usrAfterAdd, nil)

                    status, err := u.Apply(context.Background())

                    m.AssertCalled(t, "AddUser", u.Username, &options)

                    assert.NoError(t, err)
                    assert.Equal(t, fmt.Sprintf("added user %s", u.Username), status.Messages()[0])
                    assert.Equal(t, fmt.Sprintf("<%s>", string(user.StateAbsent)), status.Diffs()["create_home"].Original())
                    assert.Equal(t, usrAfterAdd.HomeDir, status.Diffs()["create_home"].Current())
                })

                t.Run("create_home and skel_dir", func(t *testing.T) {
                    usr := &os.User{
                        Username: fakeUsername,
                    }

                    m := &MockSystem2{}
                    u := user.NewUser(m)
                    u.Username = usr.Username
                    u.CreateHome = true
                    u.SkelDir = "/tmp/skel"
                    u.State = user.StatePresent
                    options := user.AddUserOptions{CreateHome: u.CreateHome, SkelDir: u.SkelDir}
                    usrAfterAdd := &os.User{
                        Username: u.Username,
                        HomeDir:  "/tmp/test",
                    }

                    m.userBeforeAdd = true
                    m.On("Lookup", usr.Username).Return(usr, usrAfterAdd, os.UnknownUserError(""))
                    m.On("AddUser", u.Username, &options).Return(nil)
                    m.On("Lookup", usrAfterAdd.Username).Return(usr, usrAfterAdd, nil)

                    status, err := u.Apply(context.Background())

                    m.AssertCalled(t, "AddUser", u.Username, &options)

                    assert.NoError(t, err)
                    assert.Equal(t, fmt.Sprintf("added user %s", u.Username), status.Messages()[0])
                    assert.Equal(t, fmt.Sprintf("<%s>", string(user.StateAbsent)), status.Diffs()["create_home"].Original())
                    assert.Equal(t, usrAfterAdd.HomeDir, status.Diffs()["create_home"].Current())
                    assert.Equal(t, u.SkelDir, status.Diffs()["skel_dir contents"].Original())
                    assert.Equal(t, usrAfterAdd.HomeDir, status.Diffs()["skel_dir contents"].Current())
                })
            })

            t.Run("will not attempt to add", func(t *testing.T) {
                usr := &os.User{
                    Username: fakeUsername,
                }
                grp := &os.Group{
                    Name: fakeGroupName,
                }
                m := &MockSystem{}
                u := user.NewUser(m)
                u.Username = usr.Username
                u.GroupName = grp.Name
                u.State = user.StatePresent
                options := user.AddUserOptions{}
                optErr := fmt.Sprintf("group %s does not exist", u.GroupName)

                m.On("Lookup", u.Username).Return(usr, os.UnknownUserError(""))
                m.On("LookupGroup", u.GroupName).Return(grp, os.UnknownGroupError(""))
                m.On("AddUser", u.Username, &options).Return(nil)
                status, err := u.Apply(context.Background())

                m.AssertNotCalled(t, "AddUser", u.Username, &options)
                assert.EqualError(t, err, fmt.Sprintf("will not attempt to add user %s: %s", u.Username, optErr))
                assert.Equal(t, resource.StatusCantChange, status.StatusCode())
            })

            t.Run("error adding user", func(t *testing.T) {
                usr := &os.User{
                    Username: fakeUsername,
                }
                m := &MockSystem{}
                u := user.NewUser(m)
                u.Username = usr.Username
                u.State = user.StatePresent
                options := user.AddUserOptions{}

                m.On("Lookup", u.Username).Return(usr, os.UnknownUserError(""))
                m.On("AddUser", u.Username, &options).Return(fmt.Errorf(""))
                status, err := u.Apply(context.Background())

                m.AssertCalled(t, "AddUser", u.Username, &options)
                assert.EqualError(t, err, "user add: ")
                assert.Equal(t, resource.StatusFatal, status.StatusCode())
                assert.Equal(t, fmt.Sprintf("error adding user %s", u.Username), status.Messages()[0])
            })
        })

        t.Run("modify tests", func(t *testing.T) {
            t.Run("modify user", func(t *testing.T) {
                usr := &os.User{
                    Username: currUsername,
                }
                m := &MockSystem{}
                u := user.NewUser(m)
                u.Username = usr.Username
                u.Name = "test"
                u.State = user.StatePresent
                options := user.ModUserOptions{Comment: u.Name}

                m.On("Lookup", u.Username).Return(usr, nil)
                m.On("ModUser", u.Username, &options).Return(nil)
                status, err := u.Apply(context.Background())

                m.AssertCalled(t, "ModUser", u.Username, &options)
                assert.NoError(t, err)
                assert.Equal(t, fmt.Sprintf("modified user %s", u.Username), status.Messages()[0])
            })

            t.Run("will not attempt to modify", func(t *testing.T) {
                usr := &os.User{
                    Username: currUsername,
                }
                grp := &os.Group{
                    Name: fakeGroupName,
                }
                m := &MockSystem{}
                u := user.NewUser(m)
                u.Username = usr.Username
                u.GroupName = grp.Name
                u.State = user.StatePresent
                options := user.ModUserOptions{}
                optErr := fmt.Sprintf("group %s does not exist", u.GroupName)

                m.On("Lookup", u.Username).Return(usr, nil)
                m.On("LookupGroup", u.GroupName).Return(grp, os.UnknownGroupError(""))
                m.On("ModUser", u.Username, &options).Return(nil)
                status, err := u.Apply(context.Background())

                m.AssertNotCalled(t, "ModUser", u.Username, &options)
                assert.EqualError(t, err, fmt.Sprintf("will not attempt to modify user %s: %s", u.Username, optErr))
                assert.Equal(t, resource.StatusCantChange, status.StatusCode())
            })

            t.Run("error modifying user", func(t *testing.T) {
                usr := &os.User{
                    Username: currUsername,
                }
                m := &MockSystem{}
                u := user.NewUser(m)
                u.Username = usr.Username
                u.Name = "test"
                u.State = user.StatePresent
                options := user.ModUserOptions{Comment: u.Name}

                m.On("Lookup", u.Username).Return(usr, nil)
                m.On("ModUser", u.Username, &options).Return(fmt.Errorf(""))
                status, err := u.Apply(context.Background())

                m.AssertCalled(t, "ModUser", u.Username, &options)
                assert.EqualError(t, err, "user modify: ")
                assert.Equal(t, resource.StatusFatal, status.StatusCode())
                assert.Equal(t, fmt.Sprintf("error modifying user %s", u.Username), status.Messages()[0])
            })
        })
    })

    t.Run("state=absent", func(t *testing.T) {
        t.Run("delete user", func(t *testing.T) {
            usr := &os.User{
                Username: fakeUsername,
            }
            m := &MockSystem{}
            u := user.NewUser(m)
            u.Username = usr.Username
            u.State = user.StateAbsent

            m.On("Lookup", u.Username).Return(usr, nil)
            m.On("DelUser", u.Username).Return(nil)
            status, err := u.Apply(context.Background())

            m.AssertCalled(t, "DelUser", u.Username)
            assert.NoError(t, err)
            assert.Equal(t, fmt.Sprintf("deleted user %s", u.Username), status.Messages()[0])
            assert.Equal(t, u.Username, status.Diffs()["user"].Original())
            assert.Equal(t, fmt.Sprintf("<%s>", string(user.StateAbsent)), status.Diffs()["user"].Current())
            assert.True(t, status.HasChanges())
        })

        t.Run("no delete-error deleting user", func(t *testing.T) {
            usr := &os.User{
                Username: fakeUsername,
            }
            m := &MockSystem{}
            u := user.NewUser(m)
            u.Username = usr.Username
            u.State = user.StateAbsent

            m.On("Lookup", u.Username).Return(usr, nil)
            m.On("DelUser", u.Username).Return(fmt.Errorf(""))
            status, err := u.Apply(context.Background())

            m.AssertCalled(t, "DelUser", u.Username)
            assert.EqualError(t, err, "user delete: ")
            assert.Equal(t, resource.StatusFatal, status.StatusCode())
            assert.Equal(t, fmt.Sprintf("error deleting user %s", u.Username), status.Messages()[0])
        })

        t.Run("no delete-will not attempt delete", func(t *testing.T) {
            usr := &os.User{
                Username: fakeUsername,
            }
            m := &MockSystem{}
            u := user.NewUser(m)
            u.Username = usr.Username
            u.State = user.StateAbsent

            m.On("Lookup", u.Username).Return(usr, os.UnknownUserError(""))
            m.On("DelUser", u.Username).Return(nil)
            status, err := u.Apply(context.Background())

            m.AssertNotCalled(t, "DelUser", u.Username)
            assert.NoError(t, err)
            assert.Equal(t, resource.StatusNoChange, status.StatusCode())
        })
    })

    t.Run("state unknown", func(t *testing.T) {
        usr := &os.User{
            Username: fakeUsername,
            Uid:      fakeUID,
        }
        m := &MockSystem{}
        u := user.NewUser(m)
        u.Username = usr.Username
        u.UID = usr.Uid
        u.State = "test"
        options := user.AddUserOptions{UID: u.UID}

        m.On("Lookup", u.Username).Return(usr, nil)
        m.On("LookupID", u.UID).Return(usr, nil)
        m.On("AddUser", u.Username, &options).Return(nil)
        m.On("DelUser", u.Username).Return(nil)
        status, err := u.Apply(context.Background())

        m.AssertNotCalled(t, "AddUser", u.Username, &options)
        m.AssertNotCalled(t, "DelUser", u.Username)
        assert.EqualError(t, err, fmt.Sprintf("user: unrecognized state %s", u.State))
        assert.Equal(t, resource.StatusFatal, status.StatusCode())
    })
}

// TestDiffAdd tests DiffAdd for user
func TestDiffAdd(t *testing.T) {
    t.Parallel()

    zone := time.FixedZone(time.Now().In(time.Local).Zone())
    expiryString := "1996-12-12"
    expiry, err := time.ParseInLocation(user.ShortForm, expiryString, zone)
    require.NoError(t, err)

    t.Run("set all options", func(t *testing.T) {
        u := user.NewUser(new(user.System))
        u.Username = fakeUsername
        u.UID = fakeUID
        u.GroupName = existingGroupName
        u.Name = "test"
        u.CreateHome = true
        u.SkelDir = "/tmp/skel"
        u.HomeDir = "/tmp/test"
        u.Expiry = expiry
        status := resource.NewStatus()

        expected := &user.AddUserOptions{
            UID:        u.UID,
            Group:      u.GroupName,
            Comment:    u.Name,
            CreateHome: u.CreateHome,
            SkelDir:    u.SkelDir,
            Directory:  u.HomeDir,
            Expiry:     expiryString,
        }

        options, err := u.DiffAdd(status)

        assert.NoError(t, err)
        assert.Equal(t, expected, options)
        assert.Equal(t, resource.StatusWillChange, status.StatusCode())
        assert.True(t, status.HasChanges())
        assert.Equal(t, fmt.Sprintf("<%s>", string(user.StateAbsent)), status.Diffs()["username"].Original())
        assert.Equal(t, u.Username, status.Diffs()["username"].Current())
        assert.Equal(t, fmt.Sprintf("<%s>", string(user.StateAbsent)), status.Diffs()["group"].Original())
        assert.Equal(t, u.GroupName, status.Diffs()["group"].Current())
        assert.Equal(t, fmt.Sprintf("<%s>", string(user.StateAbsent)), status.Diffs()["uid"].Original())
        assert.Equal(t, u.UID, status.Diffs()["uid"].Current())
        assert.Equal(t, fmt.Sprintf("<%s>", string(user.StateAbsent)), status.Diffs()["comment"].Original())
        assert.Equal(t, u.Name, status.Diffs()["comment"].Current())
        assert.Equal(t, fmt.Sprintf("<%s>", string(user.StateAbsent)), status.Diffs()["create_home"].Original())
        assert.Equal(t, u.HomeDir, status.Diffs()["create_home"].Current())
        assert.Equal(t, u.SkelDir, status.Diffs()["skel_dir contents"].Original())
        assert.Equal(t, u.HomeDir, status.Diffs()["skel_dir contents"].Current())
        assert.Equal(t, "<default home>", status.Diffs()["home_dir name"].Original())
        assert.Equal(t, u.HomeDir, status.Diffs()["home_dir name"].Current())
        assert.Equal(t, "<default expiry>", status.Diffs()["expiry"].Original())
        assert.Equal(t, expiryString, status.Diffs()["expiry"].Current())
    })

    t.Run("username", func(t *testing.T) {
        t.Run("group exists-provide groupname", func(t *testing.T) {
            u := user.NewUser(new(user.System))
            u.Username = existingGroupName
            u.GroupName = existingGroupName
            status := resource.NewStatus()

            expected := &user.AddUserOptions{
                Group: u.GroupName,
            }

            options, err := u.DiffAdd(status)

            assert.NoError(t, err)
            assert.Equal(t, expected, options)
            assert.Equal(t, resource.StatusWillChange, status.StatusCode())
            assert.True(t, status.HasChanges())
            assert.Equal(t, fmt.Sprintf("<%s>", string(user.StateAbsent)), status.Diffs()["username"].Original())
            assert.Equal(t, u.Username, status.Diffs()["username"].Current())
            assert.Equal(t, fmt.Sprintf("<%s>", string(user.StateAbsent)), status.Diffs()["group"].Original())
            assert.Equal(t, u.GroupName, status.Diffs()["group"].Current())
        })

        t.Run("error-group exists", func(t *testing.T) {
            u := user.NewUser(new(user.System))
            u.Username = existingGroupName
            status := resource.NewStatus()

            options, err := u.DiffAdd(status)

            assert.EqualError(t, err, fmt.Sprintf("group %s exists", u.Username))
            assert.Nil(t, options)
            assert.Equal(t, resource.StatusCantChange, status.StatusCode())
            assert.Equal(t, "if you want to add this user to that group, use the groupname field", status.Messages()[0])
            assert.True(t, status.HasChanges())
        })
    })

    t.Run("uid", func(t *testing.T) {
        t.Run("uid not found", func(t *testing.T) {
            u := user.NewUser(new(user.System))
            u.Username = fakeUsername
            u.UID = fakeUID
            status := resource.NewStatus()

            expected := &user.AddUserOptions{
                UID: u.UID,
            }

            options, err := u.DiffAdd(status)

            assert.NoError(t, err)
            assert.Equal(t, expected, options)
            assert.Equal(t, resource.StatusWillChange, status.StatusCode())
            assert.True(t, status.HasChanges())
            assert.Equal(t, fmt.Sprintf("<%s>", string(user.StateAbsent)), status.Diffs()["username"].Original())
            assert.Equal(t, u.Username, status.Diffs()["username"].Current())
            assert.Equal(t, fmt.Sprintf("<%s>", string(user.StateAbsent)), status.Diffs()["uid"].Original())
            assert.Equal(t, u.UID, status.Diffs()["uid"].Current())
        })

        t.Run("error-uid found", func(t *testing.T) {
            u := user.NewUser(new(user.System))
            u.Username = fakeUsername
            u.UID = currUID
            status := resource.NewStatus()

            options, err := u.DiffAdd(status)

            assert.EqualError(t, err, fmt.Sprintf("uid %s already exists", u.UID))
            assert.Nil(t, options)
            assert.Equal(t, resource.StatusCantChange, status.StatusCode())
            assert.True(t, status.HasChanges())
        })
    })

    t.Run("group", func(t *testing.T) {
        t.Run("with groupname", func(t *testing.T) {
            u := user.NewUser(new(user.System))
            u.Username = fakeUsername
            u.GroupName = existingGroupName
            status := resource.NewStatus()

            expected := &user.AddUserOptions{
                Group: u.GroupName,
            }

            options, err := u.DiffAdd(status)

            assert.NoError(t, err)
            assert.Equal(t, expected, options)
            assert.Equal(t, resource.StatusWillChange, status.StatusCode())
            assert.True(t, status.HasChanges())
            assert.Equal(t, fmt.Sprintf("<%s>", string(user.StateAbsent)), status.Diffs()["username"].Original())
            assert.Equal(t, u.Username, status.Diffs()["username"].Current())
            assert.Equal(t, fmt.Sprintf("<%s>", string(user.StateAbsent)), status.Diffs()["group"].Original())
            assert.Equal(t, u.GroupName, status.Diffs()["group"].Current())
        })

        t.Run("error-groupname not found", func(t *testing.T) {
            u := user.NewUser(new(user.System))
            u.Username = fakeUsername
            u.GroupName = fakeGroupName
            status := resource.NewStatus()

            options, err := u.DiffAdd(status)

            assert.EqualError(t, err, fmt.Sprintf("group %s does not exist", u.GroupName))
            assert.Nil(t, options)
            assert.Equal(t, resource.StatusCantChange, status.StatusCode())
            assert.True(t, status.HasChanges())
        })

        t.Run("with gid", func(t *testing.T) {
            u := user.NewUser(new(user.System))
            u.Username = fakeUsername
            u.GID = existingGID
            status := resource.NewStatus()

            expected := &user.AddUserOptions{
                Group: u.GID,
            }

            options, err := u.DiffAdd(status)

            assert.NoError(t, err)
            assert.Equal(t, expected, options)
            assert.Equal(t, resource.StatusWillChange, status.StatusCode())
            assert.True(t, status.HasChanges())
            assert.Equal(t, fmt.Sprintf("<%s>", string(user.StateAbsent)), status.Diffs()["username"].Original())
            assert.Equal(t, u.Username, status.Diffs()["username"].Current())
            assert.Equal(t, fmt.Sprintf("<%s>", string(user.StateAbsent)), status.Diffs()["gid"].Original())
            assert.Equal(t, u.GID, status.Diffs()["gid"].Current())
        })

        t.Run("error-gid not found", func(t *testing.T) {
            u := user.NewUser(new(user.System))
            u.Username = fakeUsername
            u.GID = fakeGID
            status := resource.NewStatus()

            options, err := u.DiffAdd(status)

            assert.EqualError(t, err, fmt.Sprintf("group gid %s does not exist", u.GID))
            assert.Nil(t, options)
            assert.Equal(t, resource.StatusCantChange, status.StatusCode())
            assert.True(t, status.HasChanges())
        })

        t.Run("user with groupname and gid", func(t *testing.T) {
            u := user.NewUser(new(user.System))
            u.Username = fakeUsername
            u.GroupName = existingGroupName
            u.GID = existingGID
            status := resource.NewStatus()

            expected := &user.AddUserOptions{
                Group: u.GroupName,
            }

            options, err := u.DiffAdd(status)

            assert.NoError(t, err)
            assert.Equal(t, expected, options)
            assert.Equal(t, resource.StatusWillChange, status.StatusCode())
            assert.True(t, status.HasChanges())
            assert.Equal(t, fmt.Sprintf("<%s>", string(user.StateAbsent)), status.Diffs()["username"].Original())
            assert.Equal(t, u.Username, status.Diffs()["username"].Current())
            assert.Equal(t, fmt.Sprintf("<%s>", string(user.StateAbsent)), status.Diffs()["group"].Original())
            assert.Equal(t, u.GroupName, status.Diffs()["group"].Current())
        })
    })

    t.Run("comment", func(t *testing.T) {
        u := user.NewUser(new(user.System))
        u.Username = fakeUsername
        u.Name = "test"
        status := resource.NewStatus()

        expected := &user.AddUserOptions{
            Comment: u.Name,
        }

        options, err := u.DiffAdd(status)

        assert.NoError(t, err)
        assert.Equal(t, expected, options)
        assert.Equal(t, resource.StatusWillChange, status.StatusCode())
        assert.True(t, status.HasChanges())
        assert.Equal(t, fmt.Sprintf("<%s>", string(user.StateAbsent)), status.Diffs()["username"].Original())
        assert.Equal(t, u.Username, status.Diffs()["username"].Current())
        assert.Equal(t, fmt.Sprintf("<%s>", string(user.StateAbsent)), status.Diffs()["comment"].Original())
        assert.Equal(t, u.Name, status.Diffs()["comment"].Current())
    })

    t.Run("directory", func(t *testing.T) {
        t.Run("create_home with home_dir", func(t *testing.T) {
            u := user.NewUser(new(user.System))
            u.Username = fakeUsername
            u.CreateHome = true
            u.HomeDir = "/tmp/test"
            u.SkelDir = "/tmp/skel"
            status := resource.NewStatus()

            expected := &user.AddUserOptions{
                CreateHome: u.CreateHome,
                SkelDir:    u.SkelDir,
                Directory:  u.HomeDir,
            }

            options, err := u.DiffAdd(status)

            assert.NoError(t, err)
            assert.Equal(t, expected, options)
            assert.Equal(t, resource.StatusWillChange, status.StatusCode())
            assert.True(t, status.HasChanges())
            assert.Equal(t, fmt.Sprintf("<%s>", string(user.StateAbsent)), status.Diffs()["username"].Original())
            assert.Equal(t, u.Username, status.Diffs()["username"].Current())
            assert.Equal(t, fmt.Sprintf("<%s>", string(user.StateAbsent)), status.Diffs()["create_home"].Original())
            assert.Equal(t, u.HomeDir, status.Diffs()["create_home"].Current())
            assert.Equal(t, "<default home>", status.Diffs()["home_dir name"].Original())
            assert.Equal(t, u.HomeDir, status.Diffs()["home_dir name"].Current())
            assert.Equal(t, u.SkelDir, status.Diffs()["skel_dir contents"].Original())
            assert.Equal(t, u.HomeDir, status.Diffs()["skel_dir contents"].Current())
        })

        t.Run("create_home with default home", func(t *testing.T) {
            u := user.NewUser(new(user.System))
            u.Username = fakeUsername
            u.CreateHome = true
            u.SkelDir = "/tmp/skel"
            status := resource.NewStatus()

            expected := &user.AddUserOptions{
                CreateHome: u.CreateHome,
                SkelDir:    u.SkelDir,
            }

            options, err := u.DiffAdd(status)

            assert.NoError(t, err)
            assert.Equal(t, expected, options)
            assert.Equal(t, resource.StatusWillChange, status.StatusCode())
            assert.True(t, status.HasChanges())
            assert.Equal(t, fmt.Sprintf("<%s>", string(user.StateAbsent)), status.Diffs()["username"].Original())
            assert.Equal(t, u.Username, status.Diffs()["username"].Current())
            assert.Equal(t, fmt.Sprintf("<%s>", string(user.StateAbsent)), status.Diffs()["create_home"].Original())
            assert.Equal(t, "<default home>", status.Diffs()["create_home"].Current())
            assert.Equal(t, u.SkelDir, status.Diffs()["skel_dir contents"].Original())
            assert.Equal(t, "<default home>", status.Diffs()["skel_dir contents"].Current())
        })

        t.Run("default home without create_home", func(t *testing.T) {
            u := user.NewUser(new(user.System))
            u.Username = fakeUsername
            u.SkelDir = "/tmp/skel"
            status := resource.NewStatus()

            expected := &user.AddUserOptions{}

            options, err := u.DiffAdd(status)

            assert.NoError(t, err)
            assert.Equal(t, expected, options)
            assert.Equal(t, resource.StatusWillChange, status.StatusCode())
            assert.True(t, status.HasChanges())
            assert.Equal(t, fmt.Sprintf("<%s>", string(user.StateAbsent)), status.Diffs()["username"].Original())
            assert.Equal(t, u.Username, status.Diffs()["username"].Current())
        })

        t.Run("home_dir without create_home", func(t *testing.T) {
            u := user.NewUser(new(user.System))
            u.Username = fakeUsername
            u.HomeDir = "/tmp/test"
            u.SkelDir = "/tmp/skel"
            status := resource.NewStatus()

            expected := &user.AddUserOptions{
                Directory: u.HomeDir,
            }

            options, err := u.DiffAdd(status)

            assert.NoError(t, err)
            assert.Equal(t, expected, options)
            assert.Equal(t, resource.StatusWillChange, status.StatusCode())
            assert.True(t, status.HasChanges())
            assert.Equal(t, fmt.Sprintf("<%s>", string(user.StateAbsent)), status.Diffs()["username"].Original())
            assert.Equal(t, u.Username, status.Diffs()["username"].Current())
            assert.Equal(t, "<default home>", status.Diffs()["home_dir name"].Original())
            assert.Equal(t, u.HomeDir, status.Diffs()["home_dir name"].Current())
        })
    })

    t.Run("no options", func(t *testing.T) {
        u := user.NewUser(new(user.System))
        u.Username = fakeUsername
        status := resource.NewStatus()

        expected := &user.AddUserOptions{}

        options, err := u.DiffAdd(status)

        assert.NoError(t, err)
        assert.Equal(t, expected, options)
        assert.Equal(t, resource.StatusWillChange, status.StatusCode())
        assert.True(t, status.HasChanges())
        assert.Equal(t, fmt.Sprintf("<%s>", string(user.StateAbsent)), status.Diffs()["username"].Original())
        assert.Equal(t, u.Username, status.Diffs()["username"].Current())
    })
}

// TestDiffDel tests DiffDel for user
func TestDiffDel(t *testing.T) {
    t.Parallel()

    t.Run("user does not exist", func(t *testing.T) {
        u := user.NewUser(new(user.System))
        u.Username = fakeUsername
        u.State = user.StateAbsent
        status := resource.NewStatus()

        usr := os.User{}

        err := u.DiffDel(status, &usr, true)

        assert.NoError(t, err)
        assert.Equal(t, resource.StatusNoChange, status.StatusCode())
        assert.False(t, status.HasChanges())
    })

    t.Run("delete user", func(t *testing.T) {
        u := user.NewUser(new(user.System))
        u.Username = currUsername
        u.State = user.StateAbsent
        status := resource.NewStatus()

        usr := os.User{Username: currUsername}

        err := u.DiffDel(status, &usr, false)

        assert.NoError(t, err)
        assert.Equal(t, resource.StatusWillChange, status.StatusCode())
        assert.Equal(t, u.Username, status.Diffs()["user"].Original())
        assert.Equal(t, fmt.Sprintf("<%s>", string(user.StateAbsent)), status.Diffs()["user"].Current())
        assert.True(t, status.HasChanges())
    })

    t.Run("uid provided", func(t *testing.T) {
        t.Run("user does not exist", func(t *testing.T) {
            u := user.NewUser(new(user.System))
            u.Username = fakeUsername
            u.UID = fakeUID
            u.State = user.StateAbsent
            status := resource.NewStatus()

            usr := os.User{}

            err := u.DiffDel(status, &usr, true)

            assert.NoError(t, err)
            assert.Equal(t, resource.StatusNoChange, status.StatusCode())
            assert.False(t, status.HasChanges())
        })

        t.Run("uid does not exist", func(t *testing.T) {
            u := user.NewUser(new(user.System))
            u.Username = currUsername
            u.UID = fakeUID
            u.State = user.StateAbsent
            status := resource.NewStatus()

            usr := os.User{Username: currUsername, Uid: currUID}

            err := u.DiffDel(status, &usr, false)

            assert.EqualError(t, err, fmt.Sprintf("uid %s does not exist", u.UID))
            assert.Equal(t, resource.StatusCantChange, status.StatusCode())
            assert.True(t, status.HasChanges())
        })

        t.Run("uid belongs to different user", func(t *testing.T) {
            u := user.NewUser(new(user.System))
            u.Username = currUsername
            u.UID = existingUID
            u.State = user.StateAbsent
            status := resource.NewStatus()

            usr := os.User{Username: currUsername, Uid: currUID}

            err := u.DiffDel(status, &usr, false)

            assert.EqualError(t, err, fmt.Sprintf("uid %s belongs to different user", u.UID))
            assert.Equal(t, resource.StatusCantChange, status.StatusCode())
            assert.True(t, status.HasChanges())
        })

        t.Run("delete user", func(t *testing.T) {
            u := user.NewUser(new(user.System))
            u.Username = currUsername
            u.UID = currUID
            u.State = user.StateAbsent
            status := resource.NewStatus()

            err := u.DiffDel(status, currUser, false)

            assert.NoError(t, err)
            assert.Equal(t, resource.StatusWillChange, status.StatusCode())
            assert.Equal(t, u.Username, status.Diffs()["user"].Original())
            assert.Equal(t, fmt.Sprintf("<%s>", string(user.StateAbsent)), status.Diffs()["user"].Current())
            assert.True(t, status.HasChanges())
        })
    })
}

// TestDiffMod tests DiffMod for user
func TestDiffMod(t *testing.T) {
    t.Parallel()

    t.Run("set all options", func(t *testing.T) {
        u := user.NewUser(new(user.System))
        u.Username = currUsername
        u.NewUsername = fakeUsername
        u.UID = fakeUID
        u.GID = existingGID
        u.Name = "test"
        u.HomeDir = "/tmp/test"
        u.MoveDir = true
        status := resource.NewStatus()

        expected := &user.ModUserOptions{
            Username:  u.NewUsername,
            UID:       u.UID,
            Group:     u.GID,
            Comment:   u.Name,
            Directory: u.HomeDir,
            MoveDir:   true,
        }

        options, err := u.DiffMod(status, currUser)

        assert.NoError(t, err)
        assert.Equal(t, expected, options)
        assert.Equal(t, resource.StatusWillChange, status.StatusCode())
        assert.True(t, status.HasChanges())
        assert.Equal(t, currUser.Username, status.Diffs()["username"].Original())
        assert.Equal(t, u.NewUsername, status.Diffs()["username"].Current())
        assert.Equal(t, currGID, status.Diffs()["gid"].Original())
        assert.Equal(t, u.GID, status.Diffs()["gid"].Current())
        assert.Equal(t, currUID, status.Diffs()["uid"].Original())
        assert.Equal(t, u.UID, status.Diffs()["uid"].Current())
        assert.Equal(t, currUser.Name, status.Diffs()["comment"].Original())
        assert.Equal(t, u.Name, status.Diffs()["comment"].Current())
        assert.Equal(t, currUser.HomeDir, status.Diffs()["home_dir name"].Original())
        assert.Equal(t, u.HomeDir, status.Diffs()["home_dir name"].Current())
        assert.Equal(t, currUser.HomeDir, status.Diffs()["home_dir contents"].Original())
        assert.Equal(t, u.HomeDir, status.Diffs()["home_dir contents"].Current())
    })

    t.Run("username", func(t *testing.T) {
        t.Run("user not found", func(t *testing.T) {
            u := user.NewUser(new(user.System))
            u.Username = currUsername
            u.NewUsername = fakeUsername
            status := resource.NewStatus()

            expected := &user.ModUserOptions{
                Username: u.NewUsername,
            }

            options, err := u.DiffMod(status, currUser)

            assert.NoError(t, err)
            assert.Equal(t, expected, options)
            assert.Equal(t, resource.StatusWillChange, status.StatusCode())
            assert.True(t, status.HasChanges())
            assert.Equal(t, currUser.Username, status.Diffs()["username"].Original())
            assert.Equal(t, u.NewUsername, status.Diffs()["username"].Current())
        })

        t.Run("error-user found", func(t *testing.T) {
            u := user.NewUser(new(user.System))
            u.Username = currUsername
            u.NewUsername = existingUser.Username
            status := resource.NewStatus()

            options, err := u.DiffMod(status, currUser)

            assert.EqualError(t, err, fmt.Sprintf("user %s already exists", u.NewUsername))
            assert.Nil(t, options)
            assert.Equal(t, resource.StatusCantChange, status.StatusCode())
            assert.True(t, status.HasChanges())
        })
    })

    t.Run("uid", func(t *testing.T) {
        t.Run("uid not found", func(t *testing.T) {
            u := user.NewUser(new(user.System))
            u.Username = currUsername
            u.UID = fakeUID
            status := resource.NewStatus()

            expected := &user.ModUserOptions{
                UID: u.UID,
            }

            options, err := u.DiffMod(status, currUser)

            assert.NoError(t, err)
            assert.Equal(t, expected, options)
            assert.Equal(t, resource.StatusWillChange, status.StatusCode())
            assert.True(t, status.HasChanges())
            assert.Equal(t, currUID, status.Diffs()["uid"].Original())
            assert.Equal(t, u.UID, status.Diffs()["uid"].Current())
        })

        t.Run("error-uid found", func(t *testing.T) {
            u := user.NewUser(new(user.System))
            u.Username = currUsername
            u.UID = existingUID
            status := resource.NewStatus()

            options, err := u.DiffMod(status, currUser)

            assert.EqualError(t, err, fmt.Sprintf("uid %s already exists", u.UID))
            assert.Nil(t, options)
            assert.Equal(t, resource.StatusCantChange, status.StatusCode())
            assert.True(t, status.HasChanges())
        })

        t.Run("current uid-other options", func(t *testing.T) {
            u := user.NewUser(new(user.System))
            u.Username = currUsername
            u.UID = currUID
            u.Name = "test"
            status := resource.NewStatus()

            expected := &user.ModUserOptions{
                Comment: "test",
            }

            options, err := u.DiffMod(status, currUser)

            assert.NoError(t, err)
            assert.Equal(t, expected, options)
            assert.Equal(t, resource.StatusWillChange, status.StatusCode())
            assert.True(t, status.HasChanges())
            assert.Equal(t, currUser.Name, status.Diffs()["comment"].Original())
            assert.Equal(t, u.Name, status.Diffs()["comment"].Current())
            assert.Equal(t, expected.UID, "")
        })

        t.Run("current uid-no other options", func(t *testing.T) {
            u := user.NewUser(new(user.System))
            u.Username = currUsername
            u.UID = currUID
            status := resource.NewStatus()

            expected := &user.ModUserOptions{}

            options, err := u.DiffMod(status, currUser)

            assert.NoError(t, err)
            assert.Equal(t, expected, options)
            assert.Equal(t, resource.StatusNoChange, status.StatusCode())
            assert.False(t, status.HasChanges())
        })
    })

    t.Run("group", func(t *testing.T) {
        t.Run("with groupname", func(t *testing.T) {
            u := user.NewUser(new(user.System))
            u.Username = currUsername
            u.GroupName = existingGroupName
            status := resource.NewStatus()

            expected := &user.ModUserOptions{
                Group: u.GroupName,
            }

            options, err := u.DiffMod(status, currUser)

            assert.NoError(t, err)
            assert.Equal(t, expected, options)
            assert.Equal(t, resource.StatusWillChange, status.StatusCode())
            assert.True(t, status.HasChanges())
            assert.Equal(t, currGroupName, status.Diffs()["group"].Original())
            assert.Equal(t, u.GroupName, status.Diffs()["group"].Current())
        })

        t.Run("error-groupname not found", func(t *testing.T) {
            u := user.NewUser(new(user.System))
            u.Username = currUsername
            u.GroupName = fakeGroupName
            status := resource.NewStatus()

            options, err := u.DiffMod(status, currUser)

            assert.EqualError(t, err, fmt.Sprintf("group %s does not exist", u.GroupName))
            assert.Nil(t, options)
            assert.Equal(t, resource.StatusCantChange, status.StatusCode())
            assert.True(t, status.HasChanges())
        })

        t.Run("with gid", func(t *testing.T) {
            u := user.NewUser(new(user.System))
            u.Username = currUsername
            u.GID = existingGID
            status := resource.NewStatus()

            expected := &user.ModUserOptions{
                Group: u.GID,
            }

            options, err := u.DiffMod(status, currUser)

            assert.NoError(t, err)
            assert.Equal(t, expected, options)
            assert.Equal(t, resource.StatusWillChange, status.StatusCode())
            assert.True(t, status.HasChanges())
            assert.Equal(t, currGID, status.Diffs()["gid"].Original())
            assert.Equal(t, u.GID, status.Diffs()["gid"].Current())
        })

        t.Run("error-gid not found", func(t *testing.T) {
            u := user.NewUser(new(user.System))
            u.Username = currUsername
            u.GID = fakeGID
            status := resource.NewStatus()

            options, err := u.DiffMod(status, currUser)

            assert.EqualError(t, err, fmt.Sprintf("group gid %s does not exist", u.GID))
            assert.Nil(t, options)
            assert.Equal(t, resource.StatusCantChange, status.StatusCode())
            assert.True(t, status.HasChanges())
        })

        t.Run("user with groupname and gid", func(t *testing.T) {
            u := user.NewUser(new(user.System))
            u.Username = currUsername
            u.GroupName = existingGroupName
            u.GID = existingGID
            status := resource.NewStatus()

            expected := &user.ModUserOptions{
                Group: u.GroupName,
            }

            options, err := u.DiffMod(status, currUser)

            assert.NoError(t, err)
            assert.Equal(t, expected, options)
            assert.Equal(t, resource.StatusWillChange, status.StatusCode())
            assert.True(t, status.HasChanges())
            assert.Equal(t, currGroupName, status.Diffs()["group"].Original())
            assert.Equal(t, u.GroupName, status.Diffs()["group"].Current())
        })
    })

    t.Run("comment", func(t *testing.T) {
        u := user.NewUser(new(user.System))
        u.Username = currUsername
        u.Name = "test"
        status := resource.NewStatus()

        expected := &user.ModUserOptions{
            Comment: u.Name,
        }

        options, err := u.DiffMod(status, currUser)

        assert.NoError(t, err)
        assert.Equal(t, expected, options)
        assert.Equal(t, resource.StatusWillChange, status.StatusCode())
        assert.True(t, status.HasChanges())
        assert.Equal(t, currUser.Name, status.Diffs()["comment"].Original())
        assert.Equal(t, u.Name, status.Diffs()["comment"].Current())
    })

    t.Run("directory", func(t *testing.T) {
        u := user.NewUser(new(user.System))
        u.Username = currUsername
        u.HomeDir = "/tmp/test"
        status := resource.NewStatus()

        expected := &user.ModUserOptions{
            Directory: u.HomeDir,
        }

        options, err := u.DiffMod(status, currUser)

        assert.NoError(t, err)
        assert.Equal(t, expected, options)
        assert.Equal(t, resource.StatusWillChange, status.StatusCode())
        assert.True(t, status.HasChanges())
        assert.Equal(t, currUser.HomeDir, status.Diffs()["home_dir name"].Original())
        assert.Equal(t, u.HomeDir, status.Diffs()["home_dir name"].Current())
    })

    t.Run("expiry", func(t *testing.T) {
        zone := time.FixedZone(time.Now().In(time.Local).Zone())
        currExpiryStr := "1996-12-11"
        newExpiryStr := "1996-12-12"

        currExpiry, err := time.ParseInLocation(user.ShortForm, currExpiryStr, zone)
        require.NoError(t, err)
        newExpiry, err := time.ParseInLocation(user.ShortForm, newExpiryStr, zone)
        require.NoError(t, err)
        neverExpiry, err := time.ParseInLocation(user.ShortForm, user.MaxTime, zone)
        require.NoError(t, err)

        t.Run("date", func(t *testing.T) {
            m := &MockSystem{}
            u := user.NewUser(m)
            u.Username = currUsername
            u.Expiry = newExpiry
            status := resource.NewStatus()

            expected := &user.ModUserOptions{
                Expiry: newExpiry.Format(user.ShortForm),
            }

            m.On("LookupUserExpiry", u.Username).Return(currExpiry, nil)

            options, err := u.DiffMod(status, currUser)

            m.AssertCalled(t, "LookupUserExpiry", u.Username)
            assert.NoError(t, err)
            assert.Equal(t, expected, options)
            assert.Equal(t, resource.StatusWillChange, status.StatusCode())
            assert.True(t, status.HasChanges())
            assert.Equal(t, currExpiryStr, status.Diffs()["expiry"].Original())
            assert.Equal(t, newExpiryStr, status.Diffs()["expiry"].Current())
        })

        t.Run("never", func(t *testing.T) {
            m := &MockSystem{}
            u := user.NewUser(m)
            u.Username = currUsername
            u.Expiry = newExpiry
            status := resource.NewStatus()

            expected := &user.ModUserOptions{
                Expiry: newExpiry.Format(user.ShortForm),
            }

            m.On("LookupUserExpiry", u.Username).Return(neverExpiry, nil)

            options, err := u.DiffMod(status, currUser)

            m.AssertCalled(t, "LookupUserExpiry", u.Username)
            assert.NoError(t, err)
            assert.Equal(t, expected, options)
            assert.Equal(t, resource.StatusWillChange, status.StatusCode())
            assert.True(t, status.HasChanges())
            assert.Equal(t, "never", status.Diffs()["expiry"].Original())
            assert.Equal(t, newExpiryStr, status.Diffs()["expiry"].Current())
        })
    })

    t.Run("no options", func(t *testing.T) {
        u := user.NewUser(new(user.System))
        u.Username = currUsername
        status := resource.NewStatus()

        expected := &user.ModUserOptions{}

        options, err := u.DiffMod(status, currUser)

        assert.NoError(t, err)
        assert.Equal(t, expected, options)
        assert.Equal(t, resource.StatusNoChange, status.StatusCode())
        assert.False(t, status.HasChanges())
    })
}

// setUid is used to find a uid that exists, but is not
// a match for the current user name (currUsername).
func setUid() (string, error) {
    for i := 0; i <= maxUID; i++ {
        uid := strconv.Itoa(i)
        user, err := os.LookupId(uid)
        if err == nil && user.Username != currUsername {
            return uid, nil
        }
    }
    return "", fmt.Errorf("setUid: could not set uid")
}

// setFakeUid is used to set a uid that does not exist.
func setFakeUid() (string, error) {
    for i := minUID; i <= maxUID; i++ {
        uid := strconv.Itoa(i)
        _, err := os.LookupId(uid)
        if err != nil {
            return uid, nil
        }
    }
    return "", fmt.Errorf("setFakeUid: could not set uid")
}

// setGid is used to find a gid that exists, but is not
// the gid for the current user.
func setGid() (string, error) {
    for i := 0; i <= maxGID; i++ {
        gid := strconv.Itoa(i)
        _, err := os.LookupGroupId(gid)
        if err == nil && gid != currGID {
            return gid, nil
        }
    }
    return "", fmt.Errorf("setGid: could not set gid")
}

// setFakeGid is used to set a gid that does not exist.
func setFakeGid() (string, error) {
    for i := minGID; i <= maxGID; i++ {
        gid := strconv.Itoa(i)
        _, err := os.LookupGroupId(gid)
        if err != nil {
            return gid, nil
        }
    }
    return "", fmt.Errorf("setFakeGid: could not set gid")
}

// MockSystem is a mock implementation of user.System
type MockSystem struct {
    mock.Mock
}

// AddUser adds a user
func (m *MockSystem) AddUser(name string, options *user.AddUserOptions) error {
    args := m.Called(name, options)
    return args.Error(0)
}

// DelUser deletes a user
func (m *MockSystem) DelUser(name string) error {
    args := m.Called(name)
    return args.Error(0)
}

// ModUser modifies a user
func (m *MockSystem) ModUser(name string, options *user.ModUserOptions) error {
    args := m.Called(name, options)
    return args.Error(0)
}

// LookupUserExpiry looks up a user's expiry
func (m *MockSystem) LookupUserExpiry(name string) (time.Time, error) {
    args := m.Called(name)
    return args.Get(0).(time.Time), args.Error(1)
}

// Lookup looks up a user by name
func (m *MockSystem) Lookup(name string) (*os.User, error) {
    args := m.Called(name)
    return args.Get(0).(*os.User), args.Error(1)
}

// LookupID looks up a user by ID
func (m *MockSystem) LookupID(uid string) (*os.User, error) {
    args := m.Called(uid)
    return args.Get(0).(*os.User), args.Error(1)
}

// LookupGroup looks up a group by name
func (m *MockSystem) LookupGroup(name string) (*os.Group, error) {
    args := m.Called(name)
    return args.Get(0).(*os.Group), args.Error(1)
}

// LookupGroupID looks up a group by ID
func (m *MockSystem) LookupGroupID(gid string) (*os.Group, error) {
    args := m.Called(gid)
    return args.Get(0).(*os.Group), args.Error(1)
}

// MockSystem2 is a mock implementation of user.System, with a special
// implementation of Lookup
type MockSystem2 struct {
    mock.Mock
    MockSystem
    userBeforeAdd bool
}

// Lookup looks up a user by name
// Based on the value of userBeforeAdd, a user either before the add or after
// the add is returned
func (m *MockSystem2) Lookup(name string) (*os.User, error) {
    args := m.Called(name)
    if m.userBeforeAdd {
        m.userBeforeAdd = false
        return args.Get(0).(*os.User), args.Error(2)
    }
    return args.Get(1).(*os.User), args.Error(2)
}

// AddUser adds a user
func (m *MockSystem2) AddUser(name string, options *user.AddUserOptions) error {
    args := m.Called(name, options)
    return args.Error(0)
}