require 'spec_helper'
require 'shared_contexts'
require 'albacore/paths'
require 'albacore/nuget_model'
require 'albacore/application'

##  Table of contents
#
# - Structure of Metadata
# - Package#to_xml
# - Package#from_xml
# - Package#from_xxproj_file (full fw)
# - Package#from_xxproj_file with metadata/title in proj file (full fw)
# - Package#with_metadata
# - Package#from_xxproj_file with packages.config (full fw)
# - Package#from_xxproj_file with dependent project (full fw)
#

describe Albacore::NugetModel::Metadata do
  [ :id, :version, :authors, :title, :description, :summary, :language,
    :project_url, :license_url, :release_notes, :owners,
    :require_license_acceptance, :copyright, :tags, :dependencies,
    :framework_assemblies ].each do |prop|
    it "responds to :#{prop}" do
      expect(subject).to respond_to(prop)
    end
  end

  describe "adding dependency w/o group" do
    before do 
      subject.add_dependency 'DepId', '[3.4.5, 4.0)', '', false
    end

    let :dep do
      subject.dependencies['DepId']
    end

    it "contains the dependency version" do
      expect(dep.version).to eq '[3.4.5, 4.0)'
    end

    it "contains the dependency id" do
      expect(dep.id).to eq 'DepId'
    end

    it "has group=false from invocation" do
      expect(dep.group).to eq false
    end

    it "defaults to target_framework=''" do
      expect(dep.target_framework).to be_empty
    end

    it "contains only one dependency" do
      expect(subject.dependencies.length).to eq 1
    end

    describe "#to_xml" do
      let :xml do
        subject.to_xml
      end

      it "generates a list" do
        expect(xml).to include("  <dependencies>\n    <dependency id=\"DepId\" version=\"[3.4.5, 4.0)\"/>")
      end
    end
  end

  describe "adding framework dependency" do
    before do
      subject.add_framework_dependency 'System.Transactions', '2.0.0' 
    end

    let :dep do
      subject.framework_assemblies.first[1]
    end

    it "should contain the dependency id" do
      expect(dep.id).to eq('System.Transactions')
    end

    it "should contain the dependency version" do
      expect(dep.version).to eq('2.0.0')
    end

    it "should contain a single dependency" do
      expect(subject.framework_assemblies.length).to eq(1)
    end
  end

  describe "when adding dependency w/ groups" do
    before do
      subject.add_dependency 'Dep', '[1.2.3, 2.0)', 'net461'
      subject.add_dependency 'Dep', '[1.2.3, 2.0)', 'netstandard2.0'
    end

    it "has both deps" do
      expect(subject.dependencies.length).to eq 2
    end

    it "rejects non-grouped dependencies (after grouped)" do
      expect(lambda {
        subject.add_dependency 'Hepp', '1.2.3', '', false
      }).to raise_error(ArgumentError)
    end

    describe "#to_xml" do
      let :xml do
        subject.to_xml
      end

      it "generates a list" do
        expected = <<XML
  <dependencies>
    <group targetFramework="net461">
      <dependency id="DepId" version="[3.4.5, 4.0)" />
    </group>
    <group targetFramework="netstandard2.0">
      <dependency id="DepId" version="[3.4.5, 4.0)" />
    </group>
  </dependencies>
XML
        expect(Nokogiri::XML(xml, &:noblanks).to_xml).to \
          include(Nokogiri::XML(StringIO.new(xml), &:noblanks).to_xml)
      end
    end
  end

  describe "inverse rejection (no group, then group)" do
    before do
      subject.add_dependency 'Dep', '[1.2.3, 2.0)', '', false
    end

    it "rejects grouped dependencies (after non-grouped)" do
      expect(lambda {
        subject.add_dependency 'Hepp', '1.2.3', '', true
      }).to raise_error(ArgumentError)
    end
  end
end

describe Albacore::NugetModel::Package, "#to_xml" do
  it "should be newable" do
    expect(subject).to_not be_nil
  end

  [:metadata, :files, :to_xml, :to_xml_builder].each do |prop|
    it "should respond to #{prop}" do
      expect(subject).to respond_to(prop)
    end
  end

  it "should generate default metadata" do
    expect(subject.to_xml).to include('<metadata')
  end

  it "should not generate default files" do
    expect(subject.to_xml).to_not include('<files')
  end
end

describe Albacore::NugetModel::Package, "#from_xml" do
  let :dir do
    File.basename(__FILE__)
  end

  let :xml do
    <<XML
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
  <metadata>
    <id>Example</id>
    <version>1.2.3.4</version>
    <authors>Mr.Example</authors>
    <owners>Ms.Example</owners>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>Example package</description>
    <summary>Example package summary</summary>
    <releaseNotes>Used for specs</releaseNotes>
    <copyright>none</copyright>
    <tags>example spec</tags>
    <dependencies>
      <group>
        <dependency id="FSharp.Core" version="[4.1, 4.2)"/>
      </group>
      <group targetFramework="netstandard1.6">
        <dependency id="System.Net" version="[1.1, 2.0)"/>
      </group>
    </dependencies>
  </metadata>
  <files>
    <file src="Full/bin/Debug/*.dll" target="lib/net45"/>
    <file src="Full/bin/Debug/*.pdb" target="lib/net45"/>
    <file src="Silverlight/bin/Debug/*.dll" target="lib/sl40"/> 
    <file src="Silverlight/bin/Debug/*.pdb" target="lib/sl40"/> 
    <file src="**/*.cs" target="src"/>
  </files>
</package>
XML
  end

  let :parser do
    io = StringIO.new xml
    Nokogiri::XML(io)
  end

  let :ns do
    { ng: 'http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd' }
  end

  subject do
    package = Albacore::NugetModel::Package.from_xml xml
    package
  end

  it "exists" do
    expect(subject).to_not be_nil
  end

  it "has identical metadata props" do
    parser.
      xpath('./ng:metadata', ns).
      children.
      reject { |n| n.name == 'dependencies' }.
      reject { |n| n.text? }.
      each do |node|
      name = Albacore::NugetModel::Metadata.underscore node.name
      expect(subject.metadata.send(:"#{name}")).to eq(node.inner_text.chomp)
    end
  end

  # on Windows this fails due to replacement of path separators (by design)
  unless ::Albacore.windows?
    it 'roundtrips XML' do
      expect(Nokogiri::XML(subject.to_xml, &:noblanks).to_xml).to \
        eq(Nokogiri::XML(StringIO.new(xml), &:noblanks).to_xml)
    end
  end

  describe "all dependencies" do
    it "should have the SampleDependency dependency of the XML above" do
      parser.xpath('./ng:metadata/ng:dependencies', ns).children.reject{ |c| c.text? }.each do |dep|
        expect(subject.metadata.dependencies[dep['id']]).to_not be_nil
      end
    end 
  end

  it "should have all the files of the XML above" do
    expect(subject.files.length).to eq(5)
  end

  it "should have a dep on SampleDependency version 1.0" do
    expect(subject.metadata.dependencies['FSharp.Core']).to_not be_nil
  end
end

describe Albacore::NugetModel::Package, "#from_xxproj_file (full) => Package" do
  let :projfile do
    curr = File.dirname(__FILE__)
    File.join curr, "testdata", "Project", "Project.fsproj"
  end

  subject do
    Albacore::NugetModel::Package.from_xxproj_file projfile
  end

  include_context 'package_metadata_dsl'

  it "should find Name element" do
    expect(m.id).to eq 'Project'
  end

  it "should not find Version element" do
    expect(m.version).to eq nil
  end

  it "should find Authors element" do
    expect(m.authors).to eq "Henrik Feldt"
  end

  it 'should have the same title as <Name />' do
    expect(m.title).to eq 'Project'
  end

  describe "with sources" do
    subject do
      Albacore::NugetModel::Package.from_xxproj_file projfile, :sources => true
    end

    has_file 'Library1.fs', 'src/Library1.fs'
    has_file 'bin/Debug/Project.dll', 'lib/net451'
    has_file 'bin/Debug/Project.pdb', 'lib/net451'
  end
end

describe Albacore::NugetModel::Package, "#from_xxproj_file (core) => Package" do
  let :projfile do
    curr = File.dirname(__FILE__)
    File.join curr, "testdata", "console-core-argu", "ConsoleArgu.fsproj"
  end

  subject do
    Albacore::NugetModel::Package.from_xxproj_file projfile
  end

  include_context 'package_metadata_dsl'

  let :frameworks do
    subject
      .metadata
      .dependencies
      .group_by { |k, dep| dep.target_framework }
      .map { |g| g[0] }
  end

  it "targets netstandard2.0 and net461" do
    expect(frameworks).to eq %w|netstandard2.0 net461|
  end

  it "has groups" do
    h = subject.metadata.dependencies
    h.each do |key, depspec|
      expect(depspec.group).to eq true
    end
  end

  has_file 'bin/Debug/net461/ConsoleArgu.dll', 'lib/net461'
  has_file 'bin/Debug/netstandard2.0/ConsoleArgu.dll', 'lib/netstandard2.0'
end

describe Albacore::NugetModel::Package, "#from_xxproj_file (full) => Package w/ title" do
  let :projfile do
    curr = File.dirname(__FILE__)
    File.join curr, "testdata", "Project", "ProjectWithTitle.fsproj"
  end 

  subject do
    Albacore::NugetModel::Package.from_xxproj_file projfile
  end

  include_context 'package_metadata_dsl'

  it "should find Name element" do
    expect(m.id).to eq 'Project'
  end

  it "should not find Version element" do
    expect(m.version).to be_nil
  end

  it "should find Authors element" do
    expect(m.authors).to eq "Henrik Feldt"
  end

  it 'should use the Name tag here, too' do
    expect(m.title).to eq 'Project for Glorious Success'
  end
end

describe Albacore::NugetModel::Package, "#with_metadata (full)" do
  let :p1 do
    p = Albacore::NugetModel::Package.new.with_metadata do |m|
      m.id = 'A.B'
      m.version = '2.1.3'
      m.add_dependency 'NLog', '2.0'
    end
    p.add_file 'CodeFolder/A.cs', 'lib/CodeFolder/A.cs'
    p.add_file 'CodeFolder/*.fs', 'lib', 'AssemblyInfo.fs'
  end 
  let :p2 do
    p = Albacore::NugetModel::Package.new.with_metadata do |m|
      m.id = 'A.B.C'
      m.add_dependency 'NLog', '2.3'
      m.add_dependency 'Castle.Core', '3.0.0'
      m.owners = 'Henrik Feldt'
    end
    p.add_file 'CodeFolder/p2.fs', 'lib/CodeFolder/p2.fs'
  end
  subject do
    p1.merge_with p2
  end

  include_context 'package_metadata_dsl'

  describe "when overriding" do
    has_value :id, 'A.B.C'
    has_value :owners, 'Henrik Feldt'
    has_value :version, '2.1.3'

    has_dep 'NLog', '2.3'
    has_dep 'Castle.Core', '3.0.0'

    has_file 'CodeFolder/A.cs', 'lib/CodeFolder/A.cs'
    has_file 'CodeFolder/p2.fs', 'lib/CodeFolder/p2.fs'
    has_file 'CodeFolder/*.fs', 'lib', 'AssemblyInfo.fs'
  end
end

describe Albacore::NugetModel::Package, "(full) w/ packages.config" do

  let :projfile do
    curr = File.dirname(__FILE__)
    File.join curr, "testdata", "TestingDependencies", "Sample.Commands", "Sample.Commands.fsproj"
  end 

  subject do
    Albacore::NugetModel::Package.from_xxproj_file projfile,
      :known_projects => %w[Sample.Core],
      :version        => '2.3.0', # TODO: [2.3.0, 3.0.0)
      :configuration  => 'Debug'
  end

  include_context 'package_metadata_dsl'

  # from fsproj
  has_dep 'Sample.Core', '2.3.0', 'net40' # TODO: [2.3.0, 3.0.0)

  # from packages.config
  has_dep 'Magnum', '2.1.0', 'net40' # TODO: [2.3.0, 3.0.0)
  has_dep 'MassTransit', '2.8.0', 'net40' # TODO: [2.3.0, 3.0.0)
  has_dep 'Newtonsoft.Json', '5.0.6', 'net40' # TODO: [2.3.0, 3.0.0)

  # actual nuspec contents
  has_file 'bin/Debug/Sample.Commands.dll', 'lib/net40'
  has_file 'bin/Debug/Sample.Commands.xml', 'lib/net40'

  describe "#to_template" do
    let :template do
      subject.to_template
    end

    it "contains all dependencies" do
      expect(template).to include('dependencies')
      expect(template).to include('  MassTransit ~> 2.8.0')
      expect(template).to include('  Magnum ~> 2.1.0')
      expect(template).to include('  Newtonsoft.Json ~> 5.0.6')
    end
  end
end

describe Albacore::NugetModel::Package, "(full) w/ dependent project" do
  let :projfile do
    curr = File.dirname(__FILE__)
    File.join curr, "testdata", "TestingDependencies", "Sample.Commands",
              "Sample.Commands.fsproj"
  end 

  let :opts do
    { project_dependencies: false,
      known_projects:       %w[Sample.Core],
      version:             '2.3.0' }
  end

  subject do
    Albacore::NugetModel::Package.from_xxproj_file projfile, opts
  end

  include_context 'package_metadata_dsl'

  describe 'without project_dependencies' do
    # just as the opts in the main describe says
    has_not_dep 'Sample.Core'
    has_dep 'Magnum', '2.1.0', 'net40'
    has_dep 'MassTransit', '2.8.0', 'net40'
    has_dep 'Newtonsoft.Json', '5.0.6', 'net40'
    has_file 'bin/Debug/Sample.Commands.dll', 'lib/net40'
    has_file 'bin/Debug/Sample.Commands.xml', 'lib/net40'
    has_not_file 'Library.fs'
  end

  describe 'with project_dependencies' do

    let :opts do
      { project_dependencies: true,
        known_projects:       %w[Sample.Core],
        version:             '2.3.0' }
    end

    # just as the opts in the main describe says
    has_dep 'Sample.Core', '2.3.0', 'net40'
    has_dep 'Magnum', '2.1.0', 'net40'
    has_dep 'MassTransit', '2.8.0', 'net40'
    has_dep 'Newtonsoft.Json', '5.0.6', 'net40'
    has_file 'bin/Debug/Sample.Commands.dll', 'lib/net40'
    has_file 'bin/Debug/Sample.Commands.xml', 'lib/net40'
    has_not_file 'Library.fs'

  end

  describe 'without nuget_dependencies' do
    let :opts do
      { nuget_dependencies: false,
        known_projects:     %w[Sample.Core],
        version:           '2.3.0' }
    end

    has_dep 'Sample.Core', '2.3.0', 'net40'
    has_not_dep 'Magnum'
    has_not_dep 'MassTransit'
    has_not_dep 'Newtonsoft.Json'
    has_file 'bin/Debug/Sample.Commands.dll', 'lib/net40'
    has_file 'bin/Debug/Sample.Commands.xml', 'lib/net40'
    has_not_file 'Library.fs'
  end

  describe 'without sources' do
    let :opts do
      { sources:        false,
        known_projects: %w[Sample.Core],
        version:       '2.3.0' }
    end
    has_dep 'Sample.Core', '2.3.0', 'net40'
    has_dep 'Magnum', '2.1.0', 'net40'
    has_dep 'MassTransit', '2.8.0', 'net40'
    has_dep 'Newtonsoft.Json', '5.0.6', 'net40'
    has_file 'bin/Debug/Sample.Commands.dll', 'lib/net40'
    has_file 'bin/Debug/Sample.Commands.xml', 'lib/net40'
    has_not_file 'Library.fs'
  end

  describe 'with sources' do
    let :opts do
      { sources:        true,
        known_projects: %w[Sample.Core],
        version:        '2.3.0' }
    end
    has_dep 'Sample.Core', '2.3.0', 'net40'
    has_dep 'Magnum', '2.1.0', 'net40'
    has_dep 'MassTransit', '2.8.0', 'net40'
    has_dep 'Newtonsoft.Json', '5.0.6', 'net40'
    has_file 'bin/Debug/Sample.Commands.xml', 'lib/net40'
    has_file 'bin/Debug/Sample.Commands.dll', 'lib/net40'
    has_file 'bin/Debug/Sample.Commands.pdb', 'lib/net40'
    has_file 'Library.fs', 'src/Library.fs'
  end

  describe 'Release-only output' do
    # In this case we only have a Release output, and the nuget should pick the
    # single one that exists.
    # The idea is that we should succeed rather than fail, to remove friction, but
    # there should also be a warning about it in the output; that an OutputPath
    # was selected from a specific configuration that wasn't configured in the
    # task type.
    let :projfile do
      curr = File.dirname(__FILE__)
      File.join curr, "testdata", "EmptyProject", "EmptyProject.csproj"
    end 
    has_not_file 'bin/Debug/Sample.Commands.dll'
    has_not_file 'bin/Debug/EmptyProject.dll' 
    has_not_file 'bin/Debug/EmptyProject.xml' 
    has_file 'bin/Release/EmptyProject.dll', 'lib/net40'
  end
end