#! /usr/bin/env ruby
require 'spec_helper'
require 'puppet/face'

describe Puppet::Face[:help, '0.0.1'] do
  it "has a help action" do
    expect(subject).to be_action :help
  end

  it "has a default action of help" do
    expect(subject.get_action('help')).to be_default
  end

  it "accepts a call with no arguments" do
    expect {
      subject.help()
    }.to_not raise_error
  end

  it "accepts a face name" do
    expect { subject.help(:help) }.to_not raise_error
  end

  it "accepts a face and action name" do
    expect { subject.help(:help, :help) }.to_not raise_error
  end

  it "fails if more than a face and action are given" do
    expect { subject.help(:help, :help, :for_the_love_of_god) }.
      to raise_error ArgumentError
  end

  it "treats :current and 'current' identically" do
    expect(subject.help(:help, :version => :current)).to eq(
      subject.help(:help, :version => 'current')
    )
  end

  it "raises an error when the face is unavailable" do
    expect {
      subject.help(:huzzah, :bar, :version => '17.0.0')
    }.to raise_error(ArgumentError, /Could not find version 17\.0\.0/)
  end

  it "finds a face by version" do
    face = Puppet::Face[:huzzah, :current]
    expect(subject.help(:huzzah, :version => face.version)).
      to eq(subject.help(:huzzah, :version => :current))
  end

  it "raises an ArgumentError if the face raises a StandardError" do
    face = Puppet::Face[:module, :current]
    face.stubs(:short_description).raises(StandardError, "whoops")

    expect {
      subject.help(:module)
    }.to raise_error(ArgumentError, /Detail: "whoops"/)
  end

  it "raises an ArgumentError if the face raises a LoadError" do
    face = Puppet::Face[:module, :current]
    face.stubs(:short_description).raises(LoadError, "cannot load such file -- yard")

    expect {
      subject.help(:module)
    }.to raise_error(ArgumentError, /Detail: "cannot load such file -- yard"/)
  end

  context "when listing subcommands" do
    subject { Puppet::Face[:help, :current].help }

    RSpec::Matchers.define :have_a_summary do
      match do |instance|
        instance.summary.is_a?(String)
      end
    end

    # Check a precondition for the next block; if this fails you have
    # something odd in your set of face, and we skip testing things that
    # matter. --daniel 2011-04-10
    it "has at least one face with a summary" do
      expect(Puppet::Face.faces).to be_any do |name|
        Puppet::Face[name, :current].summary
      end
    end

    it "lists all faces which are runnable from the command line" do
      help_face = Puppet::Face[:help, :current]
      # The main purpose of the help face is to provide documentation for
      #  command line users.  It shouldn't show documentation for faces
      #  that can't be run from the command line, so, rather than iterating
      #  over all available faces, we need to iterate over the subcommands
      #  that are available from the command line.
      Puppet::Application.available_application_names.each do |name|
        next unless help_face.is_face_app?(name)
        next if help_face.exclude_from_docs?(name)
        face = Puppet::Face[name, :current]
        summary = face.summary

        expect(subject).to match(%r{ #{name} })
        summary and expect(subject).to match(%r{ #{name} +#{summary}})
      end
    end

    it "returns an 'unavailable' summary if the 'agent' application fails to generate help" do
      Puppet::Application['agent'].class.any_instance.stubs(:summary).raises(ArgumentError, "whoops")

      expect(subject).to match(/agent\s+! Subcommand unavailable due to error\. Check error logs\./)
    end

    it "returns an 'unavailable' summary if the legacy application raises a LoadError" do
      Puppet::Application['agent'].class.any_instance.stubs(:summary).raises(LoadError, "cannot load such file -- yard")

      expect(subject).to match(/agent\s+! Subcommand unavailable due to error\. Check error logs\./)
    end

    context "face summaries" do
      it "can generate face summaries" do
        faces = Puppet::Face.faces
        expect(faces.length).to be > 0
        faces.each do |name|
          expect(Puppet::Face[name, :current]).to have_a_summary
        end
      end

      it "returns an 'unavailable' summary if the face application raises a LoadError" do
        face = Puppet::Face[:module, :current]
        face.stubs(:summary).raises(LoadError, "cannot load such file -- yard")

        expect(Puppet::Face[:help, :current].help).to match(/module\s+! Subcommand unavailable due to error\. Check error logs\./)
      end
    end

    it "lists all legacy applications" do
      Puppet::Face[:help, :current].legacy_applications.each do |appname|
        expect(subject).to match(%r{ #{appname} })

        summary = Puppet::Face[:help, :current].horribly_extract_summary_from(appname)
        summary and expect(subject).to match(%r{ #{summary}\b})
      end
    end
  end

  context "deprecated faces" do
    it "prints a deprecation warning for deprecated faces" do
      Puppet::Face[:module, :current].stubs(:deprecated?).returns(true)
      expect(Puppet::Face[:help, :current].help(:module)).to match(/Warning: 'puppet module' is deprecated/)
    end
  end

  context "#all_application_summaries" do
    it "appends a deprecation warning for deprecated faces" do
      # Stub the module face as deprecated
      Puppet::Face[:module, :current].expects(:deprecated?).returns(true)
      result = Puppet::Face[:help, :current].all_application_summaries.each do |appname,summary|
        expect(summary).to match(/Deprecated/) if appname == 'module'
      end
    end
  end

  context "#legacy_applications" do
    subject { Puppet::Face[:help, :current].legacy_applications }

    # If we don't, these tests are ... less than useful, because they assume
    # it.  When this breaks you should consider ditching the entire feature
    # and tests, but if not work out how to fake one. --daniel 2011-04-11
    it { is_expected.to have_at_least(1).item }

    # Meh.  This is nasty, but we can't control the other list; the specific
    # bug that caused these to be listed is annoyingly subtle and has a nasty
    # fix, so better to have a "fail if you do something daft" trigger in
    # place here, I think. --daniel 2011-04-11
    %w{face_base indirection_base}.each do |name|
      it { is_expected.not_to include name }
    end
  end

  context "help for legacy applications" do
    subject { Puppet::Face[:help, :current] }
    let :appname do subject.legacy_applications.first end

    # This test is purposely generic, so that as we eliminate legacy commands
    # we don't get into a loop where we either test a face-based replacement
    # and fail to notice breakage, or where we have to constantly rewrite this
    # test and all. --daniel 2011-04-11
    it "returns the legacy help when given the subcommand" do
      help = subject.help(appname)
      expect(help).to match(/puppet-#{appname}/)
      %w{SYNOPSIS USAGE DESCRIPTION OPTIONS COPYRIGHT}.each do |heading|
        expect(help).to match(/^#{heading}$/)
      end
    end

    it "fails when asked for an action on a legacy command" do
      expect { subject.help(appname, :whatever) }.
        to raise_error ArgumentError, /Legacy subcommands don't take actions/
    end

    it "raises an ArgumentError if a legacy application raises a StandardError" do
      Puppet::Application[appname].class.any_instance.stubs(:help).raises(StandardError, "whoops")

      expect {
        subject.help(appname)
      }.to raise_error ArgumentError, /Detail: "whoops"/
    end

    it "raises an ArgumentError if a legacy application raises a LoadError" do
      Puppet::Application[appname].class.any_instance.stubs(:help).raises(LoadError, "cannot load such file -- yard")

      expect {
        subject.help(appname)
      }.to raise_error ArgumentError, /Detail: "cannot load such file -- yard"/
    end
  end
end