# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Gitlab::ImportExport::Group::TreeSaver do
  describe 'saves the group tree into a json object' do
    let_it_be(:user) { create(:user) }
    let_it_be(:group) { setup_groups }

    let(:shared) { Gitlab::ImportExport::Shared.new(group) }
    let(:export_path) { "#{Dir.tmpdir}/group_tree_saver_spec" }

    subject(:group_tree_saver) { described_class.new(group: group, current_user: user, shared: shared) }

    before_all do
      group.add_maintainer(user)
    end

    before do
      allow_next_instance_of(Gitlab::ImportExport) do |import_export|
        allow(import_export).to receive(:storage_path).and_return(export_path)
      end
    end

    after do
      FileUtils.rm_rf(export_path)
    end

    it 'saves the group successfully' do
      expect(group_tree_saver.save).to be true
    end

    it 'fails to export a group' do
      allow_next_instance_of(Gitlab::ImportExport::Json::NdjsonWriter) do |ndjson_writer|
        allow(ndjson_writer).to receive(:write_relation_array).and_raise(RuntimeError, 'exception')
      end

      expect(shared).to receive(:error).with(RuntimeError).and_call_original

      expect(group_tree_saver.save).to be false
    end

    context 'exported files' do
      before do
        group_tree_saver.save # rubocop:disable Rails/SaveBang
      end

      it 'has one group per line' do
        groups_catalog =
          File.readlines(exported_path_for('_all.ndjson'))
          .map { |line| Integer(line) }

        expect(groups_catalog.size).to eq(3)
        expect(groups_catalog).to eq(
          [
            group.id,
            group.descendants.first.id,
            group.descendants.first.descendants.first.id
          ])
      end

      it 'has a file per group' do
        group.self_and_descendants.pluck(:id).each do |id|
          group_attributes_file = exported_path_for("#{id}.json")

          expect(File.exist?(group_attributes_file)).to be(true)
        end
      end

      context 'group attributes file' do
        let(:group_attributes_file) { exported_path_for("#{group.id}.json") }
        let(:group_attributes) { ::JSON.parse(File.read(group_attributes_file)) }

        it 'has a file for each group with its attributes' do
          expect(group_attributes['description']).to eq(group.description)
          expect(group_attributes['parent_id']).to eq(group.parent_id)
        end

        shared_examples 'excluded attributes' do
          excluded_attributes = %w[
            owner_id
            created_at
            updated_at
            runners_token
            runners_token_encrypted
            saml_discovery_token
          ]

          excluded_attributes.each do |excluded_attribute|
            it 'does not contain excluded attribute' do
              expect(group_attributes).not_to include(excluded_attribute => group.public_send(excluded_attribute))
            end
          end
        end

        include_examples 'excluded attributes'
      end

      it 'has a file for each group association' do
        group.self_and_descendants do |g|
          %w[
            badges
            boards
            epics
            labels
            members
            milestones
          ].each do |association|
            path = exported_path_for(g.id.to_s, "#{association}.ndjson")
            expect(File.exist?(path)).to eq(true), "#{path} does not exist"
          end
        end
      end
    end
  end

  def exported_path_for(*file)
    File.join(group_tree_saver.full_path, 'groups', *file)
  end

  def setup_groups
    root = setup_group
    subgroup = setup_group(parent: root)
    setup_group(parent: subgroup)

    root
  end

  def setup_group(parent: nil)
    group = create(:group, description: 'description', parent: parent)
    create(:milestone, group: group)
    create(:group_badge, group: group)
    group_label = create(:group_label, group: group)
    board = create(:board, group: group, milestone_id: Milestone::Upcoming.id)
    create(:list, board: board, label: group_label)
    create(:group_badge, group: group)
    create(:label_priority, label: group_label, priority: 1)

    group
  end
end
