package iis import ( "context" "errors" "log/slog" "os" "strings" "testing" "github.com/certctl-io/certctl/internal/connector/target" ) // Phase 8 of the deploy-hardening I master bundle: IIS ValidateOnly // real implementation tests. IIS already has explicit pre-deploy // backup + post-rollback re-import semantics; the new bit is the // PowerShell health probe via Get-WebSite. type stubExecutor struct { out string err error } func (s *stubExecutor) Execute(_ context.Context, _ string) (string, error) { return s.out, s.err } func quietLogger() *slog.Logger { return slog.New(slog.NewTextHandler(os.NewFile(0, os.DevNull), &slog.HandlerOptions{Level: slog.LevelError})) } func TestIIS_ValidateOnly_GetWebSite_Succeeds(t *testing.T) { c := NewWithExecutor(&Config{SiteName: "Default Web Site"}, quietLogger(), &stubExecutor{out: "Default Web Site"}) if err := c.ValidateOnly(context.Background(), target.DeploymentRequest{}); err != nil { t.Errorf("got %v, want nil", err) } } func TestIIS_ValidateOnly_GetWebSite_Fails(t *testing.T) { c := NewWithExecutor(&Config{SiteName: "Missing"}, quietLogger(), &stubExecutor{ out: "Get-WebSite : Cannot find a Web site with name 'Missing'", err: errors.New("PowerShell exit 1"), }) err := c.ValidateOnly(context.Background(), target.DeploymentRequest{}) if err == nil { t.Fatal("expected error") } if errors.Is(err, target.ErrValidateOnlyNotSupported) { t.Errorf("got sentinel, want wrapped error: %v", err) } if !strings.Contains(err.Error(), "Cannot find") { t.Errorf("error missing PowerShell stderr: %v", err) } } func TestIIS_ValidateOnly_NilExecutor_ReturnsSentinel(t *testing.T) { c := &Connector{config: &Config{SiteName: "x"}, logger: quietLogger()} if err := c.ValidateOnly(context.Background(), target.DeploymentRequest{}); !errors.Is(err, target.ErrValidateOnlyNotSupported) { t.Errorf("got %v, want sentinel", err) } } func TestIIS_ValidateOnly_SiteNameQuoted(t *testing.T) { // Verify the script PROPERLY quotes site names with spaces (a common // IIS site name pattern). captured := "" exec := &stubExecutor{out: "Default Web Site"} c := NewWithExecutor(&Config{SiteName: "Default Web Site"}, quietLogger(), exec) // Wrap exec to capture script. c.executor = captureExec{wrapped: exec, captured: &captured} c.ValidateOnly(context.Background(), target.DeploymentRequest{}) if !strings.Contains(captured, `"Default Web Site"`) { t.Errorf("script missing quoted site name: %q", captured) } } type captureExec struct { wrapped PowerShellExecutor captured *string } func (c captureExec) Execute(ctx context.Context, script string) (string, error) { *c.captured = script return c.wrapped.Execute(ctx, script) } func TestIIS_ValidateOnly_OutputContextInError(t *testing.T) { c := NewWithExecutor(&Config{SiteName: "DWS"}, quietLogger(), &stubExecutor{ out: "WARNING: This site is in stopped state", err: errors.New("exit 1"), }) err := c.ValidateOnly(context.Background(), target.DeploymentRequest{}) if err == nil || !strings.Contains(err.Error(), "stopped state") { t.Errorf("got %v", err) } }