require 'spec_helper'
require_relative 'fixtures'

RSpec.describe 'Rack::Reducer' do
  using SpecRefinements

  let(:app) do
    lambda do |env|
      req = Rack::Request.new(env)
      res = Fixtures::ArtistReducer.apply(req.params).to_json
      [200, { 'Content-Type' => 'application/json' }, [res]]
    end
  end

  it 'responds with unfiltered data when filter params are empty' do
    get('/') do |res|
      expect(res.json.count).to eq(Fixtures::DB[:artists].count)
    end
  end

  it 'filters by a single param, e.g. name' do
    get('/artists?name=Blake') do |response|
      expect(response.body).to include('Blake Mills')
      expect(response.body).to include('James Blake')
      expect(response.body).not_to include('SZA')
    end
  end

  it 'filters by a single param, e.g. genre' do
    get('/artists?genre=electronic') do |response|
      expect(response.body).to include('Björk')
      expect(response.body).to include('James Blake')
      expect(response.body).not_to include('Blake Mills')
    end

    get '/artists?genre=soul' do |response|
      expect(response.body).to include('Janelle Monae')
      expect(response.body).not_to include('Björk')
    end
  end

  it 'chains multiple filters' do
    get('/artists?genre=electronic&name=blake') do |response|
      expect(response.body).to include('James Blake')
      expect(response.body).not_to include('Blake Mills')
    end
  end

  it 'handles falsy values' do
    get('/artists?releases=0') do |response|
      expect(response.body).to include('Chris Frank')
      expect(JSON.parse(response.body).length).to eq(1)
    end
  end

  it 'accepts nil as params' do
    expect(Fixtures::ArtistReducer.apply(nil)).to be_truthy
  end

  describe 'between requests' do
    original_count = Fixtures::DB[:artists].count

    it 'resets filter state' do
      get('/artists?name=Blake')
      get('/artists') do |res|
        expect(res.json.count).to eq(Fixtures::DB[:artists].count)
      end
    end

    it 'tracks updates to the backend' do
      get('/artists')

      Fixtures::DB[:artists][0][:name] = "Lizzo"
      Fixtures::DB[:artists] << { name: 'New Artist' }

      get('/artists') do |response|
        expect(response.json.count).to eq(original_count + 1)
        expect(response.json.dig(0, 'name')).to eq('Lizzo')
      end

      Fixtures::DB[:artists].pop
      Fixtures::DB[:artists][0][:name] = "Blake Mills"
    end
  end

  describe 'with default filters' do
    let(:app) do
      sort = ->(sort: 'name') { sort_by { |item| item[sort.to_sym] }  }
      filters = Fixtures::FILTERS + [sort]
      reducer = Rack::Reducer.new(Fixtures::DB[:artists], *filters)

      lambda do |env|
        req = Rack::Request.new(env)
        res = reducer.apply(req.params).to_json
        [200, { 'Content-Type' => 'application/json' }, [res]]
      end
    end

    it 'applies default filters' do
      get '/artists' do |response|
        name = response.json[0]['name']
        expect(name).to eq('Björk')
      end
    end

    it 'overrides default filters with values from params' do
      get '/artists?sort=genre' do |response|
        genre = response.json[0]['genre']
        expect(genre).to eq('alt-soul')
      end
    end
  end

  describe 'ad-hoc style via ::call' do
    let(:params) { { 'genre' => 'electronic', 'name' => 'blake' } }
    it 'works just like the primary style, but slower' do
      result = Rack::Reducer.call(
        params,
        dataset: Fixtures::DB[:artists],
        filters: Fixtures::FILTERS,
      )
      expect(result.count).to eq(1)
      expect(result[0][:name]).to eq('James Blake')
    end
  end

  it 'aliases ::create and ::new' do
    expect(Rack::Reducer.create({}, -> { 'hi' })).to be_a(Rack::Reducer)
  end

  it 'can filter by nested params' do
    get('/artists?prolificacy[min]=2&prolificacy[max]=3') do |response|
      expect(response.json.count).to eq(4)
    end
  end
end
