// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 //go:build unix package users import ( "errors" "os/user" "testing" "time" "github.com/hashicorp/nomad/ci" "github.com/shoenig/test/must" "oss.indeed.com/go/libtime/libtimetest" ) func TestCache_real_hit(t *testing.T) { ci.Parallel(t) c := newCache() // fresh lookup u, err := c.GetUser("nobody") must.NoError(t, err) must.NotNil(t, u) // hit again, cached value u2, err2 := c.GetUser("nobody") must.NoError(t, err2) must.NotNil(t, u2) must.True(t, u == u2) // compare pointers } func TestCache_real_miss(t *testing.T) { ci.Parallel(t) c := newCache() // fresh lookup u, err := c.GetUser("doesnotexist") must.Error(t, err) must.Nil(t, u) // hit again, cached value u2, err2 := c.GetUser("doesnotexist") must.Error(t, err2) must.Nil(t, u2) must.True(t, err == err2) // compare pointers } func TestCache_mock_hit(t *testing.T) { ci.Parallel(t) c := newCache() lookupCount := 0 // hijack the underlying lookup function with our own mock c.lookupUser = func(username string) (*user.User, error) { lookupCount++ return &user.User{Name: username}, nil } // hijack the clock with our own mock t0 := time.Now() clockCount := 0 c.clock = libtimetest.NewClockMock(t).NowMock.Set(func() time.Time { clockCount++ switch clockCount { case 1: return t0 case 2: return t0.Add(59 * time.Minute) default: return t0.Add(61 * time.Minute) } }) const username = "armon" // initial lookup u, err := c.GetUser(username) must.NoError(t, err) must.Eq(t, "armon", u.Name) must.Eq(t, 1, lookupCount) must.Eq(t, 1, clockCount) // second lookup, 59 minutes after initil lookup u2, err2 := c.GetUser(username) must.NoError(t, err2) must.Eq(t, "armon", u2.Name) must.Eq(t, 1, lookupCount) // was in cache must.Eq(t, 2, clockCount) // third lookup, 61 minutes after initial lookup (expired) u3, err3 := c.GetUser(username) must.NoError(t, err3) must.Eq(t, "armon", u3.Name) must.Eq(t, 2, lookupCount) must.Eq(t, 3, clockCount) } func TestCache_mock_miss(t *testing.T) { ci.Parallel(t) c := newCache() lookupCount := 0 lookupErr := errors.New("lookup error") // hijack the underlying lookup function with our own mock c.lookupUser = func(username string) (*user.User, error) { lookupCount++ return nil, lookupErr } // hijack the clock with our own mock t0 := time.Now() clockCount := 0 c.clock = libtimetest.NewClockMock(t).NowMock.Set(func() time.Time { clockCount++ switch clockCount { case 1: return t0 case 2: return t0.Add(59 * time.Second) default: return t0.Add(61 * time.Second) } }) const username = "armon" // initial lookup u, err := c.GetUser(username) must.ErrorIs(t, err, lookupErr) must.Nil(t, u) must.Eq(t, 1, lookupCount) must.Eq(t, 1, clockCount) // second lookup, 59 seconds after initial (still in cache) u2, err2 := c.GetUser(username) must.ErrorIs(t, err2, lookupErr) must.Nil(t, u2) must.Eq(t, 1, lookupCount) // in cache must.Eq(t, 2, clockCount) // third lookup, 61 seconds after initial (expired) u3, err3 := c.GetUser(username) must.ErrorIs(t, err3, lookupErr) must.Nil(t, u3) must.Eq(t, 2, lookupCount) must.Eq(t, 3, clockCount) }