Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions internal/agent/handler_public_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1480,6 +1480,37 @@ func (s *HandlerPublicTestSuite) TestHandleJobMessageWithSignedEnvelope() {
s.Contains(err.Error(), "no cached controller public key")
},
},
{
name: "when job data is not a signed envelope rejects distinctly",
setupPKI: func() {
m := pki.New(memfs.New(), "/tmp/pki", "agent")
s.Require().NoError(m.LoadOrGenerate())
m.SetControllerPublicKey(controllerPub)
agent.SetAgentPKIManager(s.testAgent, m)
},
cleanupPKI: func() {
agent.SetAgentPKIManager(s.testAgent, nil)
},
setupMsg: func(ctrl *gomock.Controller) jetstream.Msg {
return newTestMsg(ctrl, "jobs.query.test-agent", []byte("unwrapped-job"))
},
setupMocks: func() {
// Plain, unsigned job data: PKI is enabled, so this is
// rejected for missing an envelope rather than parsed.
s.expectNotAnswered("unwrapped-job")
s.mockJobClient.EXPECT().
GetJobData(gomock.Any(), "jobs.unwrapped-job").
Return([]byte(`{"id":"unwrapped-job","operation":{"type":"node.hostname.get"}}`), nil)

// No WriteStatusEvent/WriteJobResponse expectations: an
// unsigned envelope must be refused before touching the
// operation.
},
validateFunc: func(err error) {
s.Error(err)
s.Contains(err.Error(), "job data is not a signed envelope")
},
},
}

for _, tt := range tests {
Expand Down
19 changes: 13 additions & 6 deletions internal/telemetry/process/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,19 @@ func SetMemoryInfoFn(

// ResetMemoryInfoFn restores the default memoryInfoFn.
func ResetMemoryInfoFn() {
memoryInfoFn = func(proc *gopsutil.Process) (uint64, error) {
info, err := proc.MemoryInfo()
if err != nil {
return 0, err
}
memoryInfoFn = defaultMemoryInfoFn
}

// SetProcMemoryInfoFn overrides the procMemoryInfoFn injectable for testing.
func SetProcMemoryInfoFn(
fn func(*gopsutil.Process) (*gopsutil.MemoryInfoStat, error),
) {
procMemoryInfoFn = fn
}

return info.RSS, nil
// ResetProcMemoryInfoFn restores the default procMemoryInfoFn.
func ResetProcMemoryInfoFn() {
procMemoryInfoFn = func(proc *gopsutil.Process) (*gopsutil.MemoryInfoStat, error) {
return proc.MemoryInfo()
}
}
26 changes: 19 additions & 7 deletions internal/telemetry/process/self.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,27 @@ import (
var (
newProcessFn = gopsutil.NewProcess
cpuPercentFn = func(proc *gopsutil.Process) (float64, error) { return proc.CPUPercent() }
memoryInfoFn = func(proc *gopsutil.Process) (uint64, error) {
info, err := proc.MemoryInfo()
if err != nil {
return 0, err
}
// procMemoryInfoFn wraps the raw gopsutil call so the error branch in
// defaultMemoryInfoFn below can be exercised: gopsutil's MemoryInfo()
// never fails in practice on any platform this runs on, so the failure
// path needs its own seam to test.
procMemoryInfoFn = func(proc *gopsutil.Process) (*gopsutil.MemoryInfoStat, error) { return proc.MemoryInfo() }
memoryInfoFn = defaultMemoryInfoFn
)

return info.RSS, nil
// defaultMemoryInfoFn is the default value of memoryInfoFn, named so
// ResetMemoryInfoFn in export_test.go can restore this exact function
// rather than a duplicate closure at a different source location.
func defaultMemoryInfoFn(
proc *gopsutil.Process,
) (uint64, error) {
info, err := procMemoryInfoFn(proc)
if err != nil {
return 0, err
}
)

return info.RSS, nil
}

type provider struct {
pid int32
Expand Down
27 changes: 27 additions & 0 deletions internal/telemetry/process/self_public_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,33 @@ func (suite *ProcessPublicTestSuite) TestGetMetricsWithInjection() {
suite.Contains(err.Error(), "get memory info")
},
},
{
name: "returns error when the underlying MemoryInfo call fails",
pid: 0,
setup: func() {
process.SetNewProcessFn(func(_ int32) (*gopsutil.Process, error) {
return &gopsutil.Process{}, nil
})
process.SetCPUPercentFn(func(_ *gopsutil.Process) (float64, error) {
return 1.5, nil
})
process.SetProcMemoryInfoFn(
func(_ *gopsutil.Process) (*gopsutil.MemoryInfoStat, error) {
return nil, errors.New("memory info error")
},
)
},
teardown: func() {
process.ResetNewProcessFn()
process.ResetCPUPercentFn()
process.ResetProcMemoryInfoFn()
},
validateFunc: func(got *process.Metrics, err error) {
suite.Nil(got)
suite.Error(err)
suite.Contains(err.Error(), "get memory info")
},
},
}

for _, tc := range tests {
Expand Down
105 changes: 105 additions & 0 deletions internal/validation/validation_public_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,111 @@ func (s *ValidationPublicTestSuite) TestSigningKey() {
})
}

func (s *ValidationPublicTestSuite) TestAccountName() {
tests := []struct {
name string
field string
validateFunc func(bool)
}{
{
name: "when simple lowercase name",
field: "john",
validateFunc: func(got bool) {
s.True(got)
},
},
{
name: "when name with digits hyphen and underscore",
field: "j0hn-doe_2",
validateFunc: func(got bool) {
s.True(got)
},
},
{
name: "when machine account with trailing dollar sign",
field: "host$",
validateFunc: func(got bool) {
s.True(got)
},
},
{
name: "when exactly the maximum length",
field: strings.Repeat("a", 32),
validateFunc: func(got bool) {
s.True(got)
},
},
{
name: "when one character over the maximum length",
field: strings.Repeat("a", 33),
validateFunc: func(got bool) {
s.False(got)
},
},
{
name: "when name contains an uppercase letter",
field: "John",
validateFunc: func(got bool) {
s.False(got)
},
},
{
name: "when name starts with a digit",
field: "1john",
validateFunc: func(got bool) {
s.False(got)
},
},
{
name: "when name starts with a hyphen",
field: "-john",
validateFunc: func(got bool) {
s.False(got)
},
},
{
name: "when name contains a slash",
field: "jo/hn",
validateFunc: func(got bool) {
s.False(got)
},
},
{
name: "when name contains a space",
field: "jo hn",
validateFunc: func(got bool) {
s.False(got)
},
},
{
name: "when empty string",
field: "",
validateFunc: func(got bool) {
s.False(got)
},
},
}

for _, tt := range tests {
s.Run(tt.name, func() {
_, ok := validation.Var(tt.field, "account_name")
tt.validateFunc(ok)
})
}

s.Run("invalid name shows hint through struct validation", func() {
type nameReq struct {
Name string `validate:"required,account_name"`
}

errMsg, ok := validation.Struct(nameReq{Name: "-bad"})
s.False(ok)
s.Contains(errMsg, "account_name")
s.Contains(errMsg, "not a valid account name")
s.Contains(errMsg, "max 32 characters")
})
}

func (s *ValidationPublicTestSuite) TestAtLeastOneField() {
type allPointers struct {
Shell *string
Expand Down
Loading