# frozen_string_literal: true

require 'spec_helper'

describe ReleaseTools::ReleaseManagers::SlackClient do
  let(:slack_url)   { described_class::SLACK_URL }
  let(:slack_token) { 'my-secret-token' }

  subject(:client) do
    ClimateControl.modify(SLACK_APP_ADMIN_TOKEN: slack_token) do
      described_class.new
    end
  end

  describe 'initialize' do
    context 'when SLACK_APP_ADMIN_TOKEN is not set' do
      let(:slack_token) { nil }

      it 'raises an error' do
        expect { client }.to raise_error('Missing environment variable `SLACK_APP_ADMIN_TOKEN`')
      end
    end

    it { expect { client }.not_to raise_error }
  end

  describe '#sync_membership' do
    let(:url) { "https://slack.com/api/usergroups.users.update" }

    it 'sends a POST request with IDS and the provided token' do
      user_ids = %w[UID1 UID2 UID3]
      request = stub_request(:post, url)
                  .with(
                    headers: { Authorization: "Bearer #{slack_token}", 'Content-Type': 'application/x-www-form-urlencoded' },
                    body: hash_including(usergroup: ReleaseTools::Slack::RELEASE_MANAGERS, users: user_ids.join(','))
                  ).to_return(body: { ok: true }.to_json)

      client.sync_membership(user_ids)

      expect(request).to have_been_requested
    end

    context 'when the request is unauthorized' do
      it 'keeps track of the error' do
        request =
          stub_request(:post, url)
            .with(
              headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
              body: hash_including(usergroup: ReleaseTools::Slack::RELEASE_MANAGERS, users: 'UID1')
            ).to_return(status: 401)

        client.sync_membership(%w[UID1])

        expect(request).to have_been_requested
        expect(subject.sync_errors).to contain_exactly(an_instance_of(ReleaseTools::ReleaseManagers::Client::UnauthorizedError))
      end
    end

    context 'when the request is forbidden' do
      it 'keeps track of the error' do
        request =
          stub_request(:post, url)
            .with(body: hash_including(usergroup: ReleaseTools::Slack::RELEASE_MANAGERS, users: 'UID1'))
            .to_return(status: 403)

        client.sync_membership(%w[UID1])

        expect(request).to have_been_requested
        expect(subject.sync_errors).to contain_exactly(an_instance_of(ReleaseTools::ReleaseManagers::Client::UnauthorizedError))
      end
    end

    context "with a Bad request (or any other failure)" do
      it 'keeps track of the error' do
        request =
          stub_request(:post, url)
            .with(body: hash_including(usergroup: ReleaseTools::Slack::RELEASE_MANAGERS, users: 'UID1'))
            .to_return(status: 400)

        client.sync_membership(%w[UID1])

        expect(request).to have_been_requested
        expect(subject.sync_errors).to contain_exactly(an_instance_of(ReleaseTools::ReleaseManagers::Client::SyncError))
      end
    end

    context 'when slack returns a failure' do
      it 'keeps track of the error' do
        user_ids = %w[NOTEXISTING]
        request = stub_request(:post, url)
                    .with(
                      headers: { Authorization: "Bearer #{slack_token}" },
                      body: hash_including(usergroup: ReleaseTools::Slack::RELEASE_MANAGERS, users: 'NOTEXISTING')
                    ).to_return(status: 200, body: { ok: false, error: 'invalid_users' }.to_json)

        client.sync_membership(user_ids)

        expect(request).to have_been_requested
        expect(subject.sync_errors).to contain_exactly(an_instance_of(ReleaseTools::ReleaseManagers::Client::SyncError))
        expect(subject.sync_errors.first.message).to eq('invalid_users')
      end
    end
  end

  describe '#members' do
    let(:url) { "#{slack_url}/usergroups.users.list" }

    it 'sends a GET request with the provided token' do
      user_ids = %w[UID1 UID2 UID3]
      request = stub_request(:post, url)
                  .with(headers: { Authorization: "Bearer #{slack_token}" }, body: hash_including(usergroup: ReleaseTools::Slack::RELEASE_MANAGERS))
                  .to_return(body: { ok: true, users: user_ids }.to_json)

      expect(client.members).to eq(user_ids)
      expect(request).to have_been_requested
    end

    context 'when the request is unauthorized' do
      it 'keeps track of the error' do
        request = stub_request(:post, url)
                    .with(body: hash_including(usergroup: ReleaseTools::Slack::RELEASE_MANAGERS))
                    .to_return(status: 401)

        expect { client.members }.to raise_error(ReleaseTools::ReleaseManagers::Client::UnauthorizedError)

        expect(request).to have_been_requested
      end
    end

    context 'when the request is forbidden' do
      it 'keeps track of the error' do
        request = stub_request(:post, url)
                    .with(body: hash_including(usergroup: ReleaseTools::Slack::RELEASE_MANAGERS))
                    .to_return(status: 403)

        expect { client.members }.to raise_error(ReleaseTools::ReleaseManagers::Client::UnauthorizedError, 'Invalid token')

        expect(request).to have_been_requested
      end
    end

    context "with a Bad request (or any other failure)" do
      it 'keeps track of the error' do
        request = stub_request(:post, url).to_return(status: 400)

        expect { client.members }.to raise_error(ReleaseTools::ReleaseManagers::Client::SyncError, 'Bad Request')

        expect(request).to have_been_requested
      end
    end
  end

  describe '#join' do
    let(:user_ids) { %w[UID1 UID2 UID3] }
    let!(:members_request) do
      stub_request(:post, "#{slack_url}/usergroups.users.list")
        .with(body: hash_including(usergroup: ReleaseTools::Slack::RELEASE_MANAGERS))
        .to_return(body: { ok: true, users: user_ids }.to_json)
    end

    it 'adds a new user to the group' do
      new_user = 'UID4'

      expect(subject).to receive(:sync_membership).with(user_ids + [new_user])

      client.join(new_user)

      expect(members_request).to have_been_requested
    end

    it 'does not add users already belonging to the group' do
      new_user = user_ids[0]

      expect(subject).not_to receive(:sync_membership)

      client.join(new_user)

      expect(members_request).to have_been_requested
    end
  end

  describe '#leave' do
    let(:user_ids) { %w[UID1 UID2 UID3] }
    let!(:members_request) do
      stub_request(:post, "#{slack_url}/usergroups.users.list")
        .with(body: hash_including(usergroup: ReleaseTools::Slack::RELEASE_MANAGERS))
        .to_return(body: { ok: true, users: user_ids }.to_json)
    end

    it 'removes a user from the group' do
      new_user = user_ids[0]

      expect(subject).to receive(:sync_membership).with(user_ids - [new_user])

      client.leave(new_user)

      expect(members_request).to have_been_requested
    end

    it 'does not remove users not belonging to the group' do
      new_user = 'UID4'

      expect(subject).not_to receive(:sync_membership)

      client.leave(new_user)

      expect(members_request).to have_been_requested
    end
  end
end
