# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/

require 'rails_helper'

RSpec.describe Store::Provider::S3, authenticated_as: false, integration: true do

  around do |example|
    VCR.configure do |c|
      c.ignore_hosts 's3.eu-central-1.amazonaws.com'
      c.ignore_hosts 's3.eu-central-1.zammad.org'
      c.ignore_hosts ENV['S3_ENDPOINT'] if ENV['S3_ENDPOINT'].present?
    end
    example.run
    described_class.reset
  end

  describe '.client' do
    it 'returns an Aws::S3::Client object' do
      expect(described_class.client).to be_a(Aws::S3::Client)
    end
  end

  describe 'ping?' do
    it 'returns true' do
      expect(described_class.ping?).to be(true)
    end

    context 'when credentials are bad' do
      before do
        config = Store::Provider::S3::Config.send(:settings)
        config[:access_key_id] = 'bad'
        Store::Provider::S3::Config.instance_variable_set(:@config, config)
      end

      it 'returns false' do
        expect(described_class.ping?).to be(false)
      end
    end

    context 'when bucket is not existing' do
      before do
        config = Store::Provider::S3::Config.send(:settings)
        config[:bucket] = config[:bucket] + DateTime.now.strftime('%Q')
        Store::Provider::S3::Config.instance_variable_set(:@config, config)
      end

      it 'returns false' do
        expect(described_class.ping?).to be(false)
      end
    end

    context 'when endpoint is not reachable' do
      before do
        config = Store::Provider::S3::Config.send(:settings)
        config[:endpoint] = 'https://s3.eu-central-1.zammad.org'
        Store::Provider::S3::Config.instance_variable_set(:@config, config)
      end

      it 'returns false' do
        expect(described_class.ping?).to be(false)
      end
    end
  end

  describe '.ping!' do
    context 'when connection succeeds' do
      it 'raises no error' do
        expect { described_class.ping! }.not_to raise_error
      end
    end

    context 'when connection fails' do
      before do
        config = Store::Provider::S3::Config.send(:settings)
        config[:endpoint] = 'https://s3.eu-central-1.zammad.org'
        Store::Provider::S3::Config.instance_variable_set(:@config, config)
      end

      it 'raises an error' do
        expect { described_class.ping! }.to raise_error(Store::Provider::S3::Error)
      end
    end
  end

  describe '.reset' do
    it 'resets the client and its config', :aggregate_failures do
      described_class.reset
      expect(described_class.instance_variable_get(:@client)).to be_nil
      expect(Store::Provider::S3::Config.instance_variable_get(:@config)).to be_nil
      expect(Aws.config).to be_empty
    end
  end

  describe '.add' do
    let(:data)         { Rails.root.join('spec/fixtures/files/image/large.png').read }
    let(:sha256)       { Digest::SHA256.hexdigest(data) }

    it 'adds a file' do
      expect(described_class.add(data, sha256)).to be_truthy
    end

    context 'when connection fails' do
      before do
        config = Store::Provider::S3::Config.send(:settings)
        config[:endpoint] = 'https://s3.eu-central-1.zammad.org'
        Store::Provider::S3::Config.instance_variable_set(:@config, config)
      end

      it 'raises an error' do
        expect { described_class.add(data, sha256) }.to raise_error(Store::Provider::S3::Error)
      end
    end

    context 'when file already exists' do
      before do
        described_class.add(data, sha256)
      end

      it 'overrides file' do
        expect { described_class.add(data, sha256) }.not_to raise_error
      end
    end
  end

  describe '.upload' do
    let(:data) { 'data' * 15.megabytes }
    let(:sha256) { Digest::SHA256.hexdigest(data) }

    before do
      config = Store::Provider::S3::Config.send(:settings)
      config[:max_chunk_size] = 10.megabytes
      Store::Provider::S3::Config.instance_variable_set(:@config, config)
    end

    it 'uploads a file' do
      expect(described_class.upload(data, sha256)).to be_truthy
    end

    context 'when max_chunk_size setting is smaller 5MB' do
      before do
        config = Store::Provider::S3::Config.send(:settings)
        config[:max_chunk_size] = 2.megabytes
        Store::Provider::S3::Config.instance_variable_set(:@config, config)
      end

      it 'raises an error' do
        expect { described_class.upload(data, sha256) }.to raise_error(Store::Provider::S3::Error)
      end
    end
  end

  describe '.delete' do
    let(:data)   { Rails.root.join('spec/fixtures/files/image/large.png').read }
    let(:sha256) { Digest::SHA256.hexdigest(data) }

    it 'deletes a file' do
      described_class.add(data, sha256)
      expect(described_class.delete(sha256)).to be_truthy
    end

    context 'when connection fails' do
      before do
        config = Store::Provider::S3::Config.send(:settings)
        config[:endpoint] = 'https://s3.eu-central-1.zammad.org'
        Store::Provider::S3::Config.instance_variable_set(:@config, config)
      end

      it 'raises an error' do
        expect { described_class.delete(sha256) }.to raise_error(Store::Provider::S3::Error)
      end
    end

    context 'when file does not exist' do
      it 'raises no error' do
        expect { described_class.delete(sha256) }.not_to raise_error
      end
    end
  end

end