require 'chef_helper'

RSpec.describe 'gitlab::gitlab-rails' do
  describe 'Database settings' do
    let(:chef_run) { ChefSpec::SoloRunner.new(step_into: 'templatesymlink').converge('gitlab::default') }
    let(:database_yml_template) { chef_run.template('/var/opt/gitlab/gitlab-rails/etc/database.yml') }
    let(:database_yml_file_content) { ChefSpec::Renderer.new(chef_run, database_yml_template).content }
    let(:database_yml) { YAML.safe_load(database_yml_file_content, aliases: true, symbolize_names: true) }
    let(:default_database_settings) do
      {
        adapter: 'postgresql',
        application_name: nil,
        collation: nil,
        connect_timeout: nil,
        database: "gitlabhq_production",
        encoding: "unicode",
        host: "/var/opt/gitlab/postgresql",
        keepalives: nil,
        keepalives_count: nil,
        keepalives_idle: nil,
        keepalives_interval: nil,
        load_balancing: {
          hosts: []
        },
        password: nil,
        port: 5432,
        prepared_statements: false,
        socket: nil,
        sslca: nil,
        sslcompression: 0,
        sslmode: nil,
        sslrootcert: nil,
        statement_limit: 1000,
        tcp_user_timeout: nil,
        username: "gitlab",
        variables: {
          statement_timeout: nil
        }
      }
    end
    let(:default_content) do
      {
        main: default_database_settings.merge(database_tasks: true),
        ci: default_database_settings.merge(database_tasks: false)
      }
    end

    before do
      allow(Gitlab).to receive(:[]).and_call_original
      allow(File).to receive(:symlink?).and_call_original
    end

    context 'with default settings' do
      it 'renders database.yml with main database and default values' do
        expect(database_yml[:production]).to eq(default_content)
      end
    end

    context 'with user provided settings' do
      context 'via top level db_* keys' do
        before do
          stub_gitlab_rb(
            gitlab_rails: {
              db_database: 'foobar'
            }
          )
        end

        it 'renders database.yml with user specified values for main database' do
          expect(database_yml[:production][:main][:database]).to eq('foobar')
          expect(database_yml[:production][:main][:database_tasks]).to eq(true)
        end
      end

      context 'via top level db_* keys and overwritten main:' do
        before do
          stub_gitlab_rb(
            gitlab_rails: {
              db_database: 'foobar',
              db_database_tasks: true,
              databases: {
                main: {
                  enable: true,
                  db_database_tasks: false
                }
              }
            }
          )
        end

        it 'renders database.yml with user specified values for main database' do
          expect(database_yml[:production][:main][:database]).to eq('foobar')
          expect(database_yml[:production][:main][:database_tasks]).to eq(false)
        end
      end

      context "for main database via gitlab_rails['databases']['main'] setting" do
        before do
          stub_gitlab_rb(
            gitlab_rails: {
              databases: {
                main: {
                  enable: true,
                  db_database: 'foobar'
                }
              }
            }
          )
        end

        it 'renders database.yml with user specified values for main database' do
          expect(database_yml[:production][:main][:database]).to eq('foobar')
        end
      end

      context 'with additional databases specified' do
        context 'with ci database disabled' do
          before do
            stub_gitlab_rb(
              gitlab_rails: {
                databases: {
                  ci: {
                    enable: false
                  }
                }
              }
            )
          end

          it 'renders database.yml without ci database' do
            expect(database_yml[:production].keys).not_to include('ci')
          end
        end

        context 'when using the same database as main:' do
          before do
            stub_gitlab_rb(
              gitlab_rails: {
                databases: {
                  ci: {
                    enable: true
                  }
                }
              }
            )
          end

          it 'renders database.yml with main stanza first' do
            expect(database_yml_file_content).to match("production:\n  main:")
          end

          it 'renders database.yml with both main and additional databases using default values, but disabled database_tasks' do
            ci_content = default_content[:main].dup
            ci_content[:database_tasks] = false
            expected_output = default_content.merge(ci: ci_content)

            expect(database_yml[:production]).to eq(expected_output)
          end
        end

        context 'when using a different database to main:' do
          before do
            stub_gitlab_rb(
              gitlab_rails: {
                databases: {
                  ci: {
                    enable: true,
                    db_database: 'gitlabhq_production_ci'
                  }
                }
              }
            )
          end

          it 'renders database.yml with main stanza first' do
            expect(database_yml_file_content).to match("production:\n  main:")
          end

          it 'renders database.yml with both main and additional databases using default values, and enabled database_tasks' do
            ci_content = default_content[:main].dup
            ci_content[:database] = 'gitlabhq_production_ci'
            ci_content[:database_tasks] = true
            expected_output = default_content.merge(ci: ci_content)

            expect(database_yml[:production]).to eq(expected_output)
          end
        end

        context 'when db_database_tasks is explicitly enabled in main, but disabled in CI' do
          before do
            stub_gitlab_rb(
              gitlab_rails: {
                databases: {
                  main: {
                    db_connect_timeout: 30,
                    db_database_tasks: true
                  },
                  ci: {
                    enable: true,
                    db_host: 'patroni-ci',
                    db_connect_timeout: 50,
                    db_database_tasks: false
                  }
                }
              }
            )
          end

          it 'renders database.yml with user specified DB settings' do
            expect(database_yml[:production][:main][:connect_timeout]).to eq(30)
            expect(database_yml[:production][:main][:database_tasks]).to eq(true)
            expect(database_yml[:production][:ci][:connect_timeout]).to eq(50)
            expect(database_yml[:production][:ci][:database_tasks]).to eq(false)
          end
        end

        context 'with different settings for main database and additional databases' do
          before do
            stub_gitlab_rb(
              gitlab_rails: {
                databases: {
                  main: {
                    db_connect_timeout: 30
                  },
                  ci: {
                    db_host: 'the-ci-host',
                    enable: true,
                    db_connect_timeout: 50,
                    db_database_tasks: false
                  },
                  embedding: {
                    db_host: 'the-embedding-host',
                    enable: true,
                    db_load_balancing: {},
                    db_database_tasks: true
                  },
                  sec: {
                    db_host: 'the-sec-host',
                    enable: true,
                    db_connect_timeout: 50,
                    db_database_tasks: false
                  }
                }
              }
            )
          end

          it 'renders database.yml with user specified DB settings' do
            expect(database_yml[:production][:main][:connect_timeout]).to eq(30)
            expect(database_yml[:production][:main][:database_tasks]).to eq(true)
            expect(database_yml[:production][:main]).to have_key(:load_balancing)
            expect(database_yml[:production][:ci][:host]).to eq('the-ci-host')
            expect(database_yml[:production][:ci][:connect_timeout]).to eq(50)
            expect(database_yml[:production][:ci][:database_tasks]).to eq(false)
            expect(database_yml[:production][:embedding][:host]).to eq('the-embedding-host')
            expect(database_yml[:production][:embedding][:connect_timeout]).to eq(30)
            expect(database_yml[:production][:embedding][:load_balancing]).to eq({})
            expect(database_yml[:production][:embedding][:database_tasks]).to eq(true)
            expect(database_yml[:production][:sec][:host]).to eq('the-sec-host')
            expect(database_yml[:production][:sec][:connect_timeout]).to eq(50)
            expect(database_yml[:production][:sec][:database_tasks]).to eq(false)
          end
        end

        context 'with additional database specified but not enabled' do
          before do
            stub_gitlab_rb(
              gitlab_rails: {
                databases: {
                  main: {
                    db_connect_timeout: 30,
                  },
                  ci: {
                    db_connect_timeout: 50
                  }
                }
              }
            )
          end

          it 'renders database.yml without additional database' do
            expect(database_yml[:production].keys).not_to include('ci')
          end
        end

        context 'with invalid additional database specified' do
          before do
            stub_gitlab_rb(
              gitlab_rails: {
                databases: {
                  foobar: {
                    enable: true,
                    db_database: 'gitlabhq_foobar'
                  },
                  johndoe: {
                    db_database: 'gitlabhq_johndoe'
                  },
                  ci: {
                    enable: true,
                    db_database: 'gitlabhq_ci'
                  }
                }
              }
            )
          end

          it 'raises warning about invalid database' do
            chef_run
            expect_logged_warning("Additional database `foobar` not supported in Rails application. It will be ignored.")
          end

          it 'does not raise warning about invalid database that is not enabled' do
            chef_run
            expect(LoggingHelper.messages).not_to include(kind: :warning, message: /Additional database `johndoe` not supported/)
          end
        end
      end
    end
  end
end
