@@ -322,3 +322,69 @@ func TestParseEffective_RedactsEmbeddedCreds(t *testing.T) {
322322 t .Errorf ("effective.config should have redacted to user:****@host form, got: %q" , got )
323323 }
324324}
325+
326+ // On a Mac without Xcode Command Line Tools, /usr/bin/pip3 and /usr/bin/python3
327+ // are Apple shims that pop a GUI install prompt the moment they're invoked.
328+ // PipConfigDetector must skip them — otherwise rolling out the agent to Jamf
329+ // endpoints that don't deploy CLT triggers a Developer Tools dialog for every
330+ // user on first scan.
331+ //
332+ // We stub pip3/python3 to return SUCCESS so the assertion catches the bug: if
333+ // the guard isn't applied, detectPip will invoke the shim, get a successful
334+ // response, and set Available=true. With the guard, the shim is never invoked
335+ // and Available stays false. A test that left the stubs unset would pass even
336+ // against the unfixed code, because the mock's "no command stub" error would
337+ // be swallowed as if pip just wasn't installed.
338+ func TestPipConfigDetector_SkipsAppleStubWithoutCLT (t * testing.T ) {
339+ mock := executor .NewMock ()
340+ mock .SetGOOS ("darwin" )
341+ mock .SetAppleCLTInstalled (false )
342+ mock .SetPath ("pip3" , "/usr/bin/pip3" )
343+ mock .SetPath ("python3" , "/usr/bin/python3" )
344+ // Stub both shims to *succeed* — if the guard fails, detectPip will
345+ // consume these and mistakenly set Available=true.
346+ mock .SetCommand ("pip 24.0 from /usr/bin (python 3.12)\n " , "" , 0 , "pip3" , "--version" )
347+ mock .SetCommand ("pip 24.0 from /usr/bin (python 3.12)\n " , "" , 0 , "python3" , "-m" , "pip" , "--version" )
348+
349+ d := NewPipConfigDetector (mock )
350+ d .ownerLookup = fixedPipOwner ()
351+ d .gitTracked = func (_ context.Context , _ string ) bool { return false }
352+ d .inGitRepo = func (_ string ) bool { return false }
353+
354+ audit := d .Detect (context .Background (), nil )
355+ if audit .Available {
356+ t .Errorf ("expected Available=false when only Apple stubs are on PATH, got true (path=%q, invocation=%q) — the guard let the shim run" , audit .Path , audit .Invocation )
357+ }
358+ if audit .Effective != nil {
359+ t .Errorf ("expected Effective=nil when pip detection was skipped, got %+v" , audit .Effective )
360+ }
361+ }
362+
363+ // When CLT is installed, /usr/bin/pip3 resolves to the real CLT-shipped pip
364+ // and must be invoked normally. Confirms the guard is darwin+CLT-absent only,
365+ // not a blanket /usr/bin/ skip.
366+ func TestPipConfigDetector_DetectsUsrBinWhenCLTInstalled (t * testing.T ) {
367+ mock := executor .NewMock ()
368+ mock .SetGOOS ("darwin" )
369+ mock .SetAppleCLTInstalled (true )
370+ mock .SetPath ("pip3" , "/usr/bin/pip3" )
371+ mock .SetCommand ("pip 24.0 from /usr/bin (python 3.12)\n " , "" , 0 , "pip3" , "--version" )
372+ mock .SetCommand ("" , "" , 0 , "pip3" , "config" , "debug" )
373+ mock .SetCommand ("" , "" , 0 , "pip3" , "config" , "list" , "-v" )
374+
375+ d := NewPipConfigDetector (mock )
376+ d .ownerLookup = fixedPipOwner ()
377+ d .gitTracked = func (_ context.Context , _ string ) bool { return false }
378+ d .inGitRepo = func (_ string ) bool { return false }
379+
380+ audit := d .Detect (context .Background (), nil )
381+ if ! audit .Available {
382+ t .Fatalf ("expected Available=true with CLT installed, got false" )
383+ }
384+ if audit .Path != "/usr/bin/pip3" {
385+ t .Errorf ("expected Path=/usr/bin/pip3, got %q" , audit .Path )
386+ }
387+ if audit .Version != "24.0" {
388+ t .Errorf ("expected Version=24.0, got %q" , audit .Version )
389+ }
390+ }
0 commit comments