# frozen_string_literal: true

require 'spec_helper'

describe Resource, :type => :model do
  include_context "create user"
  
  let(:login) { "u-#{random_hex}" }
  let(:kind) { "the-kind" }
  let(:resource_id_id) { "r-#{random_hex}" }
  let(:resource_id) { "rspec:#{kind}:#{resource_id_id}"}
  let(:the_resource) { Resource.create(resource_id: resource_id, owner: the_user) }

  describe '.[]' do
    it "allows looking up by composite ids" do
      the_resource || raise # vivify
      expect(Resource['rspec', kind, resource_id_id]).to eq(the_resource)
    end
  end

  # Hideous hack to make tests pass temporarily with rotator change
  #
  #
  remove_expires_at = ->(x) do
    case x
    when Hash
      x.delete("expires_at")
      if x.key?("secrets")
        x["secrets"].map! {|y| y.delete("expires_at"); y }
      end
    when Array
      x.map! {|y| y.delete("expires_at"); y }
    end
    x
  end

  shared_examples_for "provides expected JSON" do
    specify {
      the_resource.reload
      hash = JSON.parse(the_resource.to_json)
      expect(hash.delete("created_at")).to be
      remove_expires_at.(hash)
      expect(hash).to eq(as_json.stringify_keys)
    }
  end
  
  let(:base_hash) {
    {
      id: the_resource.resource_id,
      owner: the_user.role_id,
      permissions: [],
      annotations: []
    }
  }
  
  it "account is required" do
    expect{ Resource.create(resource_id: "", owner: the_user) }.to raise_error(Sequel::CheckConstraintViolation, /(has_kind|has_account)/)
  end
  it "kind is required" do
    expect{ Resource.create(resource_id: "the-account", owner: the_user) }.to raise_error(Sequel::CheckConstraintViolation, /has_kind/)
  end
    
  context "basic object" do
    let(:as_json) { base_hash }
    it_should_behave_like "provides expected JSON"
  end
  
  context "with annotation" do
    before {
      Annotation.create(resource: the_resource, name: "name", value: "Kevin")
    }
    let(:as_json) { 
      base_hash.merge(annotations: [ { "name" => "name", "value" => "Kevin" } ])
    }
    it_should_behave_like "provides expected JSON"
  end
  context "with permission" do
    before {
      the_resource.permit("fry", the_user)
    }
    let(:as_json) { 
      base_hash.merge(permissions: [ {"privilege"=>"fry", "role"=>the_user.id} ])
    }
    it_should_behave_like "provides expected JSON"
  end
  context "with secret" do
    let(:kind) { "variable" }
    before {
      the_resource.add_secret(value: "the-value")
    }
    let(:as_json) { 
      base_hash.merge(secrets: [ {"version" => 1} ])
    }
    it_should_behave_like "provides expected JSON"
  end
  context "with corresponding role" do
    let(:the_role) { Role.create(role_id: resource_id) }
    let(:the_membership) { RoleMembership[role: the_role, member: the_user, admin_option: true, ownership: true] }
    before {
      the_role
      the_resource
    }
    it "the rolsource is granted to the owner with admin option" do
      expect(the_membership).to be
    end
    it "the role cannot be explicitly granted with the ownership flag" do
      expect { RoleMembership.create(role: the_role, member: the_user, admin_option: true, ownership: true) }.to raise_error(Sequel::UniqueConstraintViolation)
    end
    context "when the user is also explicitly granted the role" do
      let(:the_membership_again) { RoleMembership.create(role: the_role, member: the_user, admin_option: false) }
      it "the role can still be explicitly granted" do
        the_membership_again
      end
      it "the corresponding role is listed exactly once in the owner's list of roles" do
        the_membership_again
        expect(the_user.all_roles.reverse_order(:role_id).all.map(&:role_id)).to eq([ the_user, the_role ].map(&:role_id))
      end
    end
    it "the role can still be explicitly granted" do
      RoleMembership.create(role: the_role, member: the_user, admin_option: true)
    end
    it "the corresponding role is in the owner's list of roles" do
      expect(the_user.all_roles.all).to include(the_role)
    end
    context "changing the owner" do
      it "updates the role grants" do
        the_new_role = Role.create(role_id: "rspec:the-role:#{random_hex}")
        the_resource.owner = the_new_role
        the_resource.save
        expect(the_membership).to_not be
        expect(RoleMembership[role: the_role, member: the_new_role, admin_option: true, ownership: true]).to be
      end
    end
    context "deleting the resource" do
      it "revokes the role grant" do
        the_resource.delete
        expect(the_membership).to_not be
      end
    end
  end

  describe "#enforce_secrets_version_limit" do
    let(:kind) { "variable" }
    it "deletes extra secrets" do
      the_resource.add_secret(value: "v-1")
      the_resource.add_secret(value: "v-2")
      the_resource.add_secret(value: "v-3")
      expect(remove_expires_at.(the_resource.as_json['secrets'])).to eq([ 1, 2, 3 ].map{|i| { "version" => i }})

      the_resource.enforce_secrets_version_limit(2)
      the_resource.reload

      expect(remove_expires_at.(the_resource.as_json['secrets'])).to eq([ 2, 3 ].map{|i| { "version" => i }})

      the_resource.enforce_secrets_version_limit(1)
      the_resource.reload

      expect(remove_expires_at.(the_resource.as_json['secrets'])).to eq([ 3 ].map{|i| { "version" => i }})
    end
  end
end
