# frozen_string_literal: true

require_relative '../spec_helper'

describe 'Interfaces' do

  before(:all) do
    @tmpdir = SpecHelper.generate_service(['Sample'], multiple_files: true)
  end

  after(:all) do
    SpecHelper.cleanup_service(@tmpdir)
  end

  let(:client) { Sample::Client.new(stub_responses: true) }

  describe 'Resource Interfaces' do

    describe 'Service ::Resource Class' do

      describe '#client' do

        it 'constructs a default client' do
          client = double('client')
          expect(Sample::Client).to receive(:new).with({}).and_return(client)
          svc = Sample::Resource.new
          expect(svc.client).to be(client)
        end

        it 'constructs a client using options given' do
          client = double('client')
          expect(Sample::Client).to receive(:new).
            with({region: 'us-west-2'}).
            and_return(client)
          svc = Sample::Resource.new(region: 'us-west-2')
          expect(svc.client).to be(client)
        end

        it 'accepts a client' do
          client = double('client')
          svc = Sample::Resource.new(client: client)
          expect(svc.client).to be(client)
        end

      end

      describe 'actions' do

        it 'supports actions returning a client response' do
          data = client.stub_data(:get_band)
          expect(client).to receive(:get_band).with({foo:'bar'}).
            and_return(double('resp', data: data))
          svc = Sample::Resource.new(client: client)
          expect(svc.get_summary(foo: 'bar')).to be(data)
        end

        it 'supports actions returning another resource' do
          data = client.stub_data(:create_band, band:{band_name:'abc'})
          expect(client).to receive(:create_band).with({band_name:'abc'}).
            and_return(double('resp', data: data))
          svc = Sample::Resource.new(client: client)
          band = svc.create_band(band_name:'abc')
          expect(band).to be_kind_of(Sample::Band)
          expect(band.client).to be(client)
          expect(band.name).to eq('abc')
        end

        it 'supports actions returning a list of resources' do
          client.stub_responses(:book_concerts, concerts: [
            { concert_id: 'id-1' },
            { concert_id: 'id-2' }
          ])
          band = Sample::Resource.new(client: client)
          concerts = band.book_concerts(band_name: 'foo fighters')
          expect(concerts).to be_kind_of(Sample::Concert::Collection)
          expect(concerts.map(&:id)).to eq(['id-1', 'id-2'])
          expect(concerts.size).to eq(2)
          expect(concerts.length).to eq(2) # length is an alias of size
        end

      end

      describe 'has associations' do

        it 'supports has associations with identifier given as input' do
          svc = Sample::Resource.new(client: client)
          band = svc.band('band-name')
          expect(band).to be_kind_of(Sample::Band)
          expect(band.client).to be(svc.client)
          expect(band.name).to eq('band-name')
          expect(band.data_loaded?).to be(false)
        end

        it 'supports has associations without input' do
          svc = Sample::Resource.new(client: client)
          res = svc.empty_resource
          expect(res).to be_kind_of(Sample::EmptyResource)
          expect(res.client).to be(svc.client)
        end

        it 'supports nullable associations that return nil' do
          data = Struct.new(:fan_club_president)
          band = Sample::Band.new(name: 'band-name', data: data.new(nil), client: client)
          expect(band.fan_club_president).to be(nil)
        end

        it 'supports nullable associations that return a value' do
          data = Struct.new(:fan_club_president)
          band = Sample::Band.new(name: 'band-name', data: data.new('fan-name'), client: client)
          expect(band.fan_club_president).to be_kind_of(Sample::Fan)
          expect(band.fan_club_president.name).to eq('fan-name')
          expect(band.fan_club_president.client).to be(client)
        end

      end

      describe 'hasMany associations' do

        it 'returns a collection without making an API call' do
          client = double('client') # expects no method calls
          svc = Sample::Resource.new(client: client)
          bands = svc.bands
          expect(bands).to be_kind_of(Sample::Band::Collection)
          expect(bands.size).to be(nil) # size not known - requires enumeration
          expect(bands.length).to be(nil) # alias of size
        end

        it 'enumerates response pages when enumerating the collection' do
          client.stub_responses(:list_bands, [
            { bands: [{ band_name: 'band-1' }], next_token: 'token' },
            { bands: [{ band_name: 'band-2' }] },
          ])
          svc = Sample::Resource.new(client: client)
          bands = svc.bands.to_a # force enumeration
          expect(bands.size).to eq(2)
          expect(bands[0]).to be_kind_of(Sample::Band)
          expect(bands[0].name).to eq('band-1')
          expect(bands[0].client).to be(client)
          expect(bands[0].data_loaded?).to be(true)
          expect(bands[1]).to be_kind_of(Sample::Band)
          expect(bands[1].name).to eq('band-2')
          expect(bands[1].client).to be(client)
          expect(bands[1].data_loaded?).to be(true)
        end

        it 'supports operations that are not paginated' do
          client.stub_responses(:list_top_bands, top_five: { band_names: %w(
            band-1 band-2 band-3 band-4 band-5
          )})
          svc = Sample::Resource.new(client: client)
          bands = svc.top_five_bands.to_a # force enumeration
          expect(bands.size).to eq(5)
          expect(bands[0]).to be_kind_of(Sample::Band)
          expect(bands[0].name).to eq('band-1')
          expect(bands[0].client).to be(client)
          expect(bands[0].data_loaded?).to be(false)
          expect(bands[4]).to be_kind_of(Sample::Band)
          expect(bands[4].name).to eq('band-5')
          expect(bands[4].client).to be(client)
          expect(bands[4].data_loaded?).to be(false)
        end

      end
    end
  end

  describe 'Resource Classes' do

    it 'defines a class for each resource type' do
      expect(Sample.constants.sort).to eq([
        # constants for every service
        :GEM_VERSION,
        :Client,
        :ClientApi,
        :Errors,
        :Types,
        :Resource,
        :Waiters,
        # service specific resource classes
        :Band,
        :Concert,
        :EmptyResource,
        :Fan,
        :Tag,
        :Ticket,
        :TicketReservation,
      ].sort)
    end

    describe '#initialize' do

      it 'accepts identifiers as positional arguments' do
        band = Sample::Band.new('band-name', client: client)
        expect(band.name).to eq('band-name')
      end

      it 'accepts identifiers as keyword arguments' do
        band = Sample::Band.new(name:'band-name', client: client)
        expect(band.name).to eq('band-name')
      end

      it 'aliases identifiers when present in data' do
        band = Sample::Band.new(name:'band-name', client: client)
        expect(band.band_name).to eq('band-name')
      end

      it 'supports resources with numeric identifiers' do
        ticket = Sample::Ticket.new(number: 123456, client: client)
        expect(ticket.number).to eq(123456)
        expect(ticket.ticket_number).to eq(123456)
        expect {
          Sample::Ticket.new('123456')
        }.to raise_error(ArgumentError, "expected :number to be a Integer, got String")
      end

      it 'raises an ArgumentError when an identifier is missing' do
        expect {
          Sample::Band.new
        }.to raise_error(ArgumentError, 'missing required option :name')
      end

      it 'supports a resource that has no identifiers' do
        expect {
          Sample::EmptyResource.new(client: client)
        }.not_to raise_error
      end

    end

    describe '#client' do

      it 'constructs an instance of Client by default' do
        stub_const('ENV', {
          'AWS_REGION' => 'us-east-1',
          'AWS_ACCESS_KEY_ID' => 'akid',
          'AWS_SECRET_ACCESS_KEY' => 'secret',
        })
        band = Sample::Band.new('name')
        expect(band.client).to be_kind_of(Sample::Client)
      end

      it 'constructs a client from options' do
        client = double('client')
        expect(Sample::Client).to receive(:new).
          with({region:'us-west-2'}).
          and_return(client)
        band = Sample::Band.new(name:'name', region: 'us-west-2')
        expect(band.client).to be(client)
      end

      it 'passes extra options to the client constructor' do
        client = double('client')
        expect(Sample::Client).to receive(:new).with({foo:'bar'}).and_return(client)
        band = Sample::Band.new(name:'name', data: double('data'), foo: 'bar')
        expect(band.client).to be(client)
      end

      it 'accepts a client option' do
        client = double('client')
        band = Sample::Band.new('name', client: client)
        expect(band.client).to be(client)
      end

    end

    describe '#load, #reload, #data, #data_loaded? methods' do

      describe 'shape and load' do

        it '#load hydrates the resource' do
          expect(client).to receive(:get_ticket).
            twice.
            with(ticket_number: 1).
            and_return(client.stub_data(:get_ticket, ticket: {
              ticket_number: 1,
              purchase_price: 150,
            }))
          ticket = Sample::Ticket.new(1, client: client)
          ticket.load
          ticket.reload # load is aliased as #reload
          expect(ticket.purchase_price).to eq(150)
        end

        it '#data loads the resource only once returning the cached data' do
          resp = client.stub_data(:get_ticket, ticket:{purchase_price:10})
          ticket_data = resp.ticket
          expect(client).to receive(:get_ticket).once.and_return(resp)
          ticket = Sample::Ticket.new(1, client: client)
          expect(ticket.data).to be(ticket_data)
          expect(ticket.data).to be(ticket_data)
          expect(ticket.purchase_price).to eq(10)
          expect(ticket.purchase_price).to eq(10)
        end

        it '#data_loaded? returns false until loaded' do
          resp = client.stub_data(:get_ticket, ticket:{})
          expect(client).to receive(:get_ticket).once.and_return(resp)
          ticket = Sample::Ticket.new(1, client: client)
          expect(ticket.data_loaded?).to be(false)
          ticket.purchase_price # trigger load implicitly
          expect(ticket.data_loaded?).to be(true)
        end

        it 'uses data as provided to the constructor' do
          data = client.stub_data(:get_ticket, ticket:{purchase_price:10}).ticket
          expect(client).not_to receive(:get_ticket)
          ticket = Sample::Ticket.new(123, data: data, client: client)
          expect(ticket.data).to be(data)
          expect(ticket.data_loaded?).to be(true)
          expect(ticket.purchase_price).to be(10)
        end

        it 'allows data to be provided as hash' do
          data = client.stub_data(:get_ticket, ticket:{purchase_price:10}).ticket
          expect(client).not_to receive(:get_ticket)
          data_hash = data.to_h
          ticket = Sample::Ticket.new(123, data: data_hash, client: client)
          expect(ticket.data).to be(data_hash)
          expect(ticket.data_loaded?).to be(true)
          expect(ticket.purchase_price).to be(10)
        end

      end

      describe 'shape without load' do

        it '#load raises an error' do
          ticket = Sample::TicketReservation.new(1, client: client)
          expect {
            ticket.load
          }.to raise_error(NotImplementedError)
        end

        it '#data returns the shape when present' do
          data = Sample::Types::Ticket.new
          res = Sample::TicketReservation.new(123, data: data, client: client)
          expect(res.data).to be(data)
        end

        it '#data raises an error when shape not present' do
          expect {
            Sample::TicketReservation.new(123, client: client).data
          }.to raise_error(NotImplementedError)
        end

        it '#data_loaded? returns true if data is present' do
          res = Sample::TicketReservation.new(123, client: client)
          expect(res.data_loaded?).to be(false)
          res = Sample::TicketReservation.new(123, data: double('data'), client: client)
          expect(res.data_loaded?).to be(true)
        end

      end

      describe 'no shape' do

        it '#load raises an error' do
          res = Sample::EmptyResource.new(client: client)
          expect {
            res.load
          }.to raise_error(NotImplementedError)
        end

        it '#data returns an empty shape' do
          res = Sample::EmptyResource.new(client: client)
          expect(res.data).to be_kind_of(Aws::EmptyStructure)
        end

        it '#data_loaded? returns true' do
          res = Sample::EmptyResource.new(client: client)
          expect(res.data_loaded?).to be(true)
        end

      end
    end

    describe 'attributes' do

      it 'defines a read-only method for each identifier' do
        band = Sample::Band.new(name:'name', client: client)
        expect(band).to respond_to(:name)
        expect(band).not_to respond_to(:name=)
      end

      it 'defines a read-only method for each data shape member' do
        band = Sample::Band.new(name:'name', data: {:year_established => 2000}, client: client)
        expect(band).to respond_to(:band_name)
        expect(band).not_to respond_to(:band_name=)
        expect(band).to respond_to(:year_established)
        expect(band).not_to respond_to(:year_established=)
        expect(band.year_established).to eq(2000)
      end

      # deprecated method
      it 'returns all identifiers as a hash' do

        # first invocation generates a deprecation warning
        band = Sample::Band.new(name:'name', client: client)
        expect {
          band.identifiers
        }.to output(/DEPRECATION WARNING/).to_stderr
        # second invocation generates no warning, even from new instance
        band2 = Sample::Band.new(name:'name', client: client)
        expect {
          band2.identifiers
        }.not_to output.to_stderr

        expect(band.identifiers).to eq(name: 'name')
      end

    end

    describe '#exists?' do

      it 'does not responds to #exists? if there is no Exists waiter' do
        concert = Sample::Concert.new(id:'123', client: client)
        expect(concert).not_to respond_to(:exists?)
      end

      it 'responds to #exists? if there is an Exists waiter' do
        band = Sample::Band.new(name: 'name', client: client)
        expect(band).to respond_to(:exists?)
      end

      it 'polls the waiter once and returns the result' do
        # failure
        client.stub_responses(:get_band, 'ResourceNotFound')
        band = Sample::Band.new(name: 'name', client: client)
        expect(band.exists?).to be(false)
        # success
        client.stub_responses(:get_band, band: {})
        band = Sample::Band.new(name: 'name', client: client)
        expect(band.exists?).to be(true)
      end

      it 'raises unexpected errors' do
        # the error raised here is nonsensical, but it is
        # not handled by the waiter
        client.stub_responses(:get_band, 'BandAlreadyExists')
        band = Sample::Band.new(name: 'name', client: client)
        expect {
          band.exists?
        }.to raise_error(Sample::Errors::BandAlreadyExists)
      end

    end

    describe 'actions' do

      it 'supports actions returning a client response' do
        data = client.stub_data(:update_band, {})
        expect(client).to receive(:update_band).
          with({band_name: 'name', bio: 'updated-band-bio'}).
          and_return(double('resp', data: data))
        band = Sample::Band.new(name:'name', client: client)
        expect(band.update(bio: 'updated-band-bio')).to be(data)
      end

      it 'supports actions returning another resource' do
        data = client.stub_data(:create_band, band: {
          band_name: 'Horton Heard a Who',
          cover_band_for: 'The Who',
        })
        expect(client).to receive(:create_band).
          with({band_name: 'Horton Heard a Who', cover_band_for: 'The Who'}).
          and_return(double('resp', data: data))

        band = Sample::Band.new(name:'The Who', client: client)
        cover_band = band.create_cover_band(band_name: 'Horton Heard a Who')
        expect(cover_band.name).to eq('Horton Heard a Who')
        expect(cover_band.cover_band_for).to eq('The Who')
        expect(cover_band.client).to be(band.client)
      end

      it 'supports actions returning a list of resources' do
        client.stub_responses(:book_concerts, concerts: [
          { concert_id: 'id-1' },
          { concert_id: 'id-2' }
        ])
        band = Sample::Band.new(name:'name', client: client)
        concerts = band.book_concerts(band_name: 'foo fighters')
        expect(concerts).to be_kind_of(Sample::Concert::Collection)
        expect(concerts.map(&:id)).to eq(['id-1', 'id-2'])
        expect(concerts.size).to eq(2)
        expect(concerts.length).to eq(2) # length is an alias of size
      end

      it 'supports actions that construct a batch from request params' do
        expect(client).to receive(:create_tags).with({
          resources: ['band-name'],
          tags: [
            { key: 'tag-1-key', value: 'tag-1-value' },
            { key: 'tag-2-key', value: 'tag-2-value' },
          ]
        }).and_return(double('resp', data: Aws::EmptyStructure.new))
        band = Sample::Band.new(name:'band-name', client: client)
        tags = band.create_tags(tags: [
          { key: 'tag-1-key', value: 'tag-1-value' },
          { key: 'tag-2-key', value: 'tag-2-value' },
        ])
        expect(tags).to be_kind_of(Sample::Tag::Collection)
        expect(tags.size).to be(2)
        tags = tags.to_a
        expect(tags[0]).to be_kind_of(Sample::Tag)
        expect(tags[0].resource_id).to eq('band-name')
        expect(tags[0].key).to eq('tag-1-key')
        expect(tags[0].value).to eq('tag-1-value')
        expect(tags[0].client).to be(band.client)
        expect(tags[1]).to be_kind_of(Sample::Tag)
        expect(tags[1].resource_id).to eq('band-name')
        expect(tags[1].key).to eq('tag-2-key')
        expect(tags[1].value).to eq('tag-2-value')
        expect(tags[1].client).to be(band.client)
      end

    end

    describe 'has associations' do

      it 'supports has associations with identifier given as input' do
        band = Sample::Band.new(name: 'band-name', client: client)
        fan = band.fan('fan-name')
        expect(fan).to be_kind_of(Sample::Fan)
        expect(fan.favorite_band_name).to eq('band-name')
        expect(fan.name).to eq('fan-name')
        expect(fan.client).to be(fan.client)
        expect(fan.data_loaded?).to be(false)
      end

      it 'supports has associations without input' do
        fan_struct = Struct.new(:name, :age)
        data = Struct.new(:biggest_fans)
        band_data = data.new([fan_struct.new('fan-1', 20), fan_struct.new('fan-2', 21)])

        band = Sample::Band.new(name: 'band-name', data: band_data, client: client)
        fan = band.biggest_fan
        expect(fan).to be_kind_of(Sample::Fan)
        expect(fan.name).to eq('fan-1')
        expect(fan.data_loaded?).to be(true)
        expect(fan.age).to eq(20)
        expect(fan.client).to be(band.client)
      end

      it 'supports plural has associations' do
        fan_struct = Struct.new(:name, :age)
        data = Struct.new(:biggest_fans)
        band_data = data.new([fan_struct.new('fan-1', 20), fan_struct.new('fan-2', 21)])

        band = Sample::Band.new(name: 'band-name', data: band_data, client: client)
        fans = band.fan_club_leaders
        expect(fans).to be_kind_of(Sample::Fan::Collection)
        fans = fans.to_a # force enumeration
        expect(fans.size).to eq(2) # size must be known
        expect(fans[0]).to be_kind_of(Sample::Fan)
        expect(fans[0].name).to eq('fan-1')
        expect(fans[0].data_loaded?).to be(true)
        expect(fans[0].age).to eq(20)
        expect(fans[0].client).to be(band.client)
        expect(fans[1]).to be_kind_of(Sample::Fan)
        expect(fans[1].name).to eq('fan-2')
        expect(fans[1].data_loaded?).to be(true)
        expect(fans[1].age).to eq(21)
        expect(fans[1].client).to be(band.client)
      end

    end

    describe 'hasMany associations' do

      it 'returns a collection without making an API call' do
        client = double('client') # expects no method calls
        band = Sample::Band.new(name: 'name', client: client)
        fans = band.fans
        expect(fans).to be_kind_of(Sample::Fan::Collection)
        expect(fans.size).to be(nil) # size not known - requires enumeration
        expect(fans.length).to be(nil) # alias of size
      end

      it 'enumerates response pages when enumerating the collection' do
        client.stub_responses(:list_fans, [
          { fans: [{ name: 'fan-1' }], next_token: 'token' },
          { fans: [{ name: 'fan-2' }] },
        ])
        expect(client).to receive(:list_fans).
          with({favorite_band_name:'name'}).
          and_call_original
        band = Sample::Band.new(name: 'name', client: client)
        fans = band.fans.to_a # force enumeration
        expect(fans.size).to eq(2)
        expect(fans[0]).to be_kind_of(Sample::Fan)
        expect(fans[0].name).to eq('fan-1')
        expect(fans[0].client).to be(client)
        expect(fans[0].data_loaded?).to be(true)
        expect(fans[1]).to be_kind_of(Sample::Fan)
        expect(fans[1].name).to eq('fan-2')
        expect(fans[1].client).to be(client)
        expect(fans[1].data_loaded?).to be(true)
      end

      it 'supports complex filters' do
        client.stub_responses(:list_fans, { fans: [
          { name: 'fan-1' },
          { name: 'fan-2' },
        ]})
        expect(client).to receive(:list_fans).with({
          filters: [
            { name: 'favorite-band-name', values: ['band-name'] },
            { name: 'fandom-level', values: ['AAA'] },
          ],
          limit: 5
        }).and_call_original
        band = Sample::Band.new(name: 'band-name', client: client)
        band.biggest_fans.to_a # force enumeration
      end

      it 'supports complex filters' do
        client.stub_responses(:list_fans, { fans: [
          { name: 'fan-1' },
          { name: 'fan-2' },
        ]})
        expect(client).to receive(:list_fans).with({
          filters: [
            { name: 'favorite-band-name', values: ['band-name'] },
            { name: 'fandom-level', values: ['AAA'] },
          ],
          limit: 5
        }).and_call_original
        band = Sample::Band.new(name: 'band-name', client: client)
        band.biggest_fans.to_a # force enumeration
      end

      it 'deep merges incoming params with fixed params' do
        # this example is very similar to how
        # Aws::EC2::Client#describe_instances has a list of filter objets
        # with keys and values. The has many association requires lists
        # to be appended to each other for a proper merge.
        expect(client).to receive(:list_fans).with({
          filters: [
            { name: 'favorite-band-name', values: ['band-name'] },
            { name: 'fandom-level', values: ['AAA'] },
            { name: 'location', values: ['Seattle', 'Tacoma'] },
          ],
          limit: 5
        }).and_call_original
        band = Sample::Band.new(name: 'band-name', client: client)
        band.biggest_fans(
          filters: [
            { name: 'location', values: %w(Seattle Tacoma) }
          ]
        ).to_a # force enumeration
      end

    end

    describe 'waiters' do

      it 'has default #wait_until method' do
        band = Sample::Band.new(name:'band-name', client: client)
        expect(band).to respond_to(:wait_until)

        # Tag doesn't have waiter entry at `resource.json`
        tag = Sample::Tag.new(resource_id: 'foo', key: 'bar', value: 'baz', client: client)
        expect(tag).to respond_to(:wait_until)
      end

      it 'defines a single #wait_until_* method for each named waiter' do
        band = Sample::Band.new(name:'band-name', client: client)
        expect(band).to respond_to(:wait_until_exists)
        expect(band).to respond_to(:wait_until_famous)
      end

      it 'invokes the appropriate waiter' do
        waiter = double('waiter')
        expect(Sample::Waiters::BandExists).to receive(:new).
          with({client: client}).
          and_return(waiter)
        expect(waiter).to receive(:wait).with({band_name: 'band-name'})
        band = Sample::Band.new(name: 'band-name', client: client)
        band.wait_until_exists
      end

      it 'accepts configuration options' do
        waiter = double('waiter')
        expect(Sample::Waiters::BandExists).to receive(:new).with({
          client: client,
          max_attempts: 2,
          delay: 2,
        }).and_return(waiter)
        expect(waiter).to receive(:wait).with({band_name: 'band-name'})
        band = Sample::Band.new(name: 'band-name', client: client)
        band.wait_until_exists(max_attempts: 2, delay: 2)
      end

      it 'passes through params to the client waiter method' do
        waiter = double('waiter')
        expect(Sample::Waiters::BandExists).to receive(:new).with({
          client: client,
          max_attempts: 1,
        }).and_return(waiter)
        expect(waiter).to receive(:wait).with({band_name: 'band-name', extra_param: true})
        band = Sample::Band.new(name: 'band-name', client: client)
        band.exists?(extra_param: true)
      end

      it 'returns a new hydrated resource if path is given' do
        client.stub_responses(:get_band, band: { famous: true })
        band = Sample::Band.new(name: 'band-name', client: client)
        famous_band = band.wait_until_famous
        expect(famous_band).to be_kind_of(Sample::Band)
        expect(famous_band.client).to be(client)
        expect(famous_band.data_loaded?).to be(true)
      end

      it 'returns a new unhydrated resource if path is not given' do
        waiter = double('waiter')
        expect(Sample::Waiters::BandExists).to receive(:new).
          with({client: client}).
          and_return(waiter)
        expect(waiter).to receive(:wait).with({band_name: 'band-name'})
        band = Sample::Band.new(name: 'band-name', client: client)
        result = band.wait_until_exists
        expect(result).to be_kind_of(Sample::Band)
        expect(result.client).to be(client)
        expect(result.data_loaded?).to be(false)
      end

    end

    describe 'batchActions' do

      it 'raises for actions that model a resource' do
        svc = Sample::Resource.new(client: client)
        bands = svc.bands
        expect(bands).to respond_to(:batch_delete!)
      end

      it 'invokes one client request per collection batch and returns nil as response' do
        client.stub_responses(:list_bands, [
          { bands: [{ band_name: 'band-1' }], next_token: 'token' },
          { bands: [{ band_name: 'band-2' }] },
        ])
        expect(client).to receive(:delete_bands).
          with({bands: [{ band_name: 'band-1' }]}).
          ordered
        expect(client).to receive(:delete_bands).
          with({bands: [{ band_name: 'band-2' }]}).
          ordered
        svc = Sample::Resource.new(client: client)
        bands = svc.bands
        bands.batch_delete!
      end

      it 'auto-populate identifiers with list of strings' do
        client.stub_responses(:list_bands, [
          { bands: [{ band_name: 'band-1' }], next_token: 'token' },
          { bands: [{ band_name: 'band-2' }] },
        ])
        expect(client).to receive(:create_tags).with({
          resources: ['band-1'], tags:[{ key: 'tag-1-key', value: 'tag-1-value' }]
        }).ordered
        expect(client).to receive(:create_tags).with({
          resources: ['band-2'], tags:[{ key: 'tag-1-key', value: 'tag-1-value' }]
        }).ordered
        svc = Sample::Resource.new(client: client)
        bands = svc.bands
        bands.batch_create_tags(tags:[
          { key: 'tag-1-key', value: 'tag-1-value' },
        ])
      end

      it 'has a #batches method that returns a collection enumerator that responds to batch actions' do
        client.stub_responses(:list_bands, [
          { bands: [{ band_name: 'band-1' }], next_token: 'token' },
          { bands: [{ band_name: 'band-2' }] },
        ])
        expect(client).to receive(:delete_bands).
          with({bands: [{ band_name: 'band-1' }]}).
          ordered
        expect(client).to receive(:delete_bands).
          with({bands: [{ band_name: 'band-2' }]}).
          ordered
        svc = Sample::Resource.new(client: client)
        bands = svc.bands
        bands.batches.each do |batch|
          batch.batch_delete!
        end
      end

      it 'validates batch args, options must be a hash' do
        client.stub_responses(:list_bands, [
          { bands: [{ band_name: 'band-1' }], next_token: 'token' },
          { bands: [{ band_name: 'band-2' }] },
        ])
        svc = Sample::Resource.new(client: client)
        bands = svc.bands
        expect{
          bands.batch_delete!('not_hash')
        }.to raise_error(ArgumentError, "expected hash, got `String`")
      end

    end
  end
end
