# frozen_string_literal: true

require 'spec_helper'

describe API::ProjectClusters do
  include KubernetesHelpers

  let(:current_user) { create(:user) }
  let(:non_member) { create(:user) }
  let(:project) { create(:project, :repository) }

  before do
    project.add_maintainer(current_user)
  end

  describe 'GET /projects/:id/clusters' do
    let!(:extra_cluster) { create(:cluster, :provided_by_gcp, :project) }

    let!(:clusters) do
      create_list(:cluster, 5, :provided_by_gcp, :project, :production_environment,
                  projects: [project])
    end

    context 'non-authorized user' do
      it 'responds with 404' do
        get api("/projects/#{project.id}/clusters", non_member)

        expect(response).to have_gitlab_http_status(404)
      end
    end

    context 'authorized user' do
      before do
        get api("/projects/#{project.id}/clusters", current_user)
      end

      it 'responds with 200' do
        expect(response).to have_gitlab_http_status(200)
      end

      it 'includes pagination headers' do
        expect(response).to include_pagination_headers
      end

      it 'onlies include authorized clusters' do
        cluster_ids = json_response.map { |cluster| cluster['id'] }

        expect(cluster_ids).to match_array(clusters.pluck(:id))
        expect(cluster_ids).not_to include(extra_cluster.id)
      end
    end
  end

  describe 'GET /projects/:id/clusters/:cluster_id' do
    let(:cluster_id) { cluster.id }

    let(:platform_kubernetes) do
      create(:cluster_platform_kubernetes, :configured,
             namespace: 'project-namespace')
    end

    let(:cluster) do
      create(:cluster, :project, :provided_by_gcp, :with_domain,
             platform_kubernetes: platform_kubernetes,
             user: current_user,
             projects: [project])
    end

    context 'non-authorized user' do
      it 'responds with 404' do
        get api("/projects/#{project.id}/clusters/#{cluster_id}", non_member)

        expect(response).to have_gitlab_http_status(404)
      end
    end

    context 'authorized user' do
      before do
        get api("/projects/#{project.id}/clusters/#{cluster_id}", current_user)
      end

      it 'returns specific cluster' do
        expect(json_response['id']).to eq(cluster.id)
      end

      it 'returns cluster information' do
        expect(json_response['provider_type']).to eq('gcp')
        expect(json_response['platform_type']).to eq('kubernetes')
        expect(json_response['environment_scope']).to eq('*')
        expect(json_response['cluster_type']).to eq('project_type')
        expect(json_response['domain']).to eq('example.com')
      end

      it 'returns project information' do
        cluster_project = json_response['project']

        expect(cluster_project['id']).to eq(project.id)
        expect(cluster_project['name']).to eq(project.name)
        expect(cluster_project['path']).to eq(project.path)
      end

      it 'returns kubernetes platform information' do
        platform = json_response['platform_kubernetes']

        expect(platform['api_url']).to eq('https://kubernetes.example.com')
        expect(platform['namespace']).to eq('project-namespace')
        expect(platform['ca_cert']).to be_present
      end

      it 'returns user information' do
        user = json_response['user']

        expect(user['id']).to eq(current_user.id)
        expect(user['username']).to eq(current_user.username)
      end

      it 'returns GCP provider information' do
        gcp_provider = json_response['provider_gcp']

        expect(gcp_provider['cluster_id']).to eq(cluster.id)
        expect(gcp_provider['status_name']).to eq('created')
        expect(gcp_provider['gcp_project_id']).to eq('test-gcp-project')
        expect(gcp_provider['zone']).to eq('us-central1-a')
        expect(gcp_provider['machine_type']).to eq('n1-standard-2')
        expect(gcp_provider['num_nodes']).to eq(3)
        expect(gcp_provider['endpoint']).to eq('111.111.111.111')
      end

      context 'when cluster has no provider' do
        let(:cluster) do
          create(:cluster, :project, :provided_by_user,
                 projects: [project])
        end

        it 'does not include GCP provider info' do
          expect(json_response['provider_gcp']).not_to be_present
        end
      end

      context 'with non-existing cluster' do
        let(:cluster_id) { 123 }

        it 'returns 404' do
          expect(response).to have_gitlab_http_status(404)
        end
      end
    end
  end

  shared_context 'kubernetes calls stubbed' do
    before do
      stub_kubeclient_discover(api_url)
      stub_kubeclient_get_namespace(api_url, namespace: namespace)
      stub_kubeclient_get_service_account(api_url, "#{namespace}-service-account", namespace: namespace)
      stub_kubeclient_put_service_account(api_url, "#{namespace}-service-account", namespace: namespace)

      stub_kubeclient_get_secret(
        api_url,
        {
          metadata_name: "#{namespace}-token",
          token: Base64.encode64('sample-token'),
          namespace: namespace
        }
      )

      stub_kubeclient_put_secret(api_url, "#{namespace}-token", namespace: namespace)
      stub_kubeclient_get_role_binding(api_url, "gitlab-#{namespace}", namespace: namespace)
      stub_kubeclient_put_role_binding(api_url, "gitlab-#{namespace}", namespace: namespace)
    end
  end

  describe 'POST /projects/:id/clusters/user' do
    include_context 'kubernetes calls stubbed'

    let(:api_url) { 'https://kubernetes.example.com' }
    let(:namespace) { project.path }
    let(:authorization_type) { 'rbac' }

    let(:platform_kubernetes_attributes) do
      {
        api_url: api_url,
        token: 'sample-token',
        namespace: namespace,
        authorization_type: authorization_type
      }
    end

    let(:cluster_params) do
      {
        name: 'test-cluster',
        domain: 'domain.example.com',
        managed: false,
        platform_kubernetes_attributes: platform_kubernetes_attributes
      }
    end

    context 'non-authorized user' do
      it 'responds with 404' do
        post api("/projects/#{project.id}/clusters/user", non_member), params: cluster_params

        expect(response).to have_gitlab_http_status(404)
      end
    end

    context 'authorized user' do
      before do
        post api("/projects/#{project.id}/clusters/user", current_user), params: cluster_params
      end

      context 'with valid params' do
        it 'responds with 201' do
          expect(response).to have_gitlab_http_status(201)
        end

        it 'creates a new Cluster::Cluster' do
          cluster_result = Clusters::Cluster.find(json_response["id"])
          platform_kubernetes = cluster_result.platform

          expect(cluster_result).to be_user
          expect(cluster_result).to be_kubernetes
          expect(cluster_result.project).to eq(project)
          expect(cluster_result.name).to eq('test-cluster')
          expect(cluster_result.domain).to eq('domain.example.com')
          expect(cluster_result.managed).to be_falsy
          expect(platform_kubernetes.rbac?).to be_truthy
          expect(platform_kubernetes.api_url).to eq(api_url)
          expect(platform_kubernetes.namespace).to eq(namespace)
          expect(platform_kubernetes.token).to eq('sample-token')
        end
      end

      context 'when user does not indicate authorization type' do
        let(:platform_kubernetes_attributes) do
          {
            api_url: api_url,
            token: 'sample-token',
            namespace: namespace
          }
        end

        it 'defaults to RBAC' do
          cluster_result = Clusters::Cluster.find(json_response['id'])

          expect(cluster_result.platform_kubernetes.rbac?).to be_truthy
        end
      end

      context 'when user sets authorization type as ABAC' do
        let(:authorization_type) { 'abac' }

        it 'creates an ABAC cluster' do
          cluster_result = Clusters::Cluster.find(json_response['id'])

          expect(cluster_result.platform.abac?).to be_truthy
        end
      end

      context 'with invalid params' do
        let(:namespace) { 'invalid_namespace' }

        it 'responds with 400' do
          expect(response).to have_gitlab_http_status(400)
        end

        it 'does not create a new Clusters::Cluster' do
          expect(project.reload.clusters).to be_empty
        end

        it 'returns validation errors' do
          expect(json_response['message']['platform_kubernetes.namespace'].first).to be_present
        end
      end
    end

    context 'when user tries to add multiple clusters' do
      before do
        create(:cluster, :provided_by_gcp, :project,
               projects: [project])

        post api("/projects/#{project.id}/clusters/user", current_user), params: cluster_params
      end

      it 'responds with 403' do
        expect(response).to have_gitlab_http_status(403)
      end

      it 'returns an appropriate message' do
        expect(json_response['message']).to include('Instance does not support multiple Kubernetes clusters')
      end
    end
  end

  describe 'PUT /projects/:id/clusters/:cluster_id' do
    include_context 'kubernetes calls stubbed'

    let(:api_url) { 'https://kubernetes.example.com' }
    let(:namespace) { 'new-namespace' }
    let(:platform_kubernetes_attributes) { { namespace: namespace } }

    let(:update_params) do
      {
        domain: 'new-domain.com',
        platform_kubernetes_attributes: platform_kubernetes_attributes
      }
    end

    let!(:kubernetes_namespace) do
      create(:cluster_kubernetes_namespace,
             cluster: cluster,
             project: project)
    end

    let(:cluster) do
      create(:cluster, :project, :provided_by_gcp,
             projects: [project])
    end

    context 'non-authorized user' do
      it 'responds with 404' do
        put api("/projects/#{project.id}/clusters/#{cluster.id}", non_member), params: update_params

        expect(response).to have_gitlab_http_status(404)
      end
    end

    context 'authorized user' do
      before do
        put api("/projects/#{project.id}/clusters/#{cluster.id}", current_user), params: update_params

        cluster.reload
      end

      context 'with valid params' do
        it 'responds with 200' do
          expect(response).to have_gitlab_http_status(200)
        end

        it 'updates cluster attributes' do
          expect(cluster.domain).to eq('new-domain.com')
          expect(cluster.platform_kubernetes.namespace).to eq('new-namespace')
        end
      end

      context 'with invalid params' do
        let(:namespace) { 'invalid_namespace' }

        it 'responds with 400' do
          expect(response).to have_gitlab_http_status(400)
        end

        it 'does not update cluster attributes' do
          expect(cluster.domain).not_to eq('new_domain.com')
          expect(cluster.platform_kubernetes.namespace).not_to eq('invalid_namespace')
          expect(cluster.kubernetes_namespace_for(project)).not_to eq('invalid_namespace')
        end

        it 'returns validation errors' do
          expect(json_response['message']['platform_kubernetes.namespace'].first).to match('can contain only lowercase letters')
        end
      end

      context 'with a GCP cluster' do
        context 'when user tries to change GCP specific fields' do
          let(:platform_kubernetes_attributes) do
            {
              api_url: 'https://new-api-url.com',
              token: 'new-sample-token'
            }
          end

          it 'responds with 400' do
            expect(response).to have_gitlab_http_status(400)
          end

          it 'returns validation error' do
            expect(json_response['message']['platform_kubernetes.base'].first).to eq('Cannot modify managed Kubernetes cluster')
          end
        end

        context 'when user tries to change namespace' do
          let(:namespace) { 'new-namespace' }

          it 'responds with 200' do
            expect(response).to have_gitlab_http_status(200)
          end
        end
      end

      context 'with an user cluster' do
        let(:api_url) { 'https://new-api-url.com' }

        let(:cluster) do
          create(:cluster, :project, :provided_by_user,
                 projects: [project])
        end

        let(:platform_kubernetes_attributes) do
          {
            api_url: api_url,
            namespace: 'new-namespace',
            token: 'new-sample-token'
          }
        end

        let(:update_params) do
          {
            name: 'new-name',
            platform_kubernetes_attributes: platform_kubernetes_attributes
          }
        end

        it 'responds with 200' do
          expect(response).to have_gitlab_http_status(200)
        end

        it 'updates platform kubernetes attributes' do
          platform_kubernetes = cluster.platform_kubernetes

          expect(cluster.name).to eq('new-name')
          expect(platform_kubernetes.namespace).to eq('new-namespace')
          expect(platform_kubernetes.api_url).to eq('https://new-api-url.com')
          expect(platform_kubernetes.token).to eq('new-sample-token')
        end
      end

      context 'with a cluster that does not belong to user' do
        let(:cluster) { create(:cluster, :project, :provided_by_user) }

        it 'responds with 404' do
          expect(response).to have_gitlab_http_status(404)
        end
      end
    end
  end

  describe 'DELETE /projects/:id/clusters/:cluster_id' do
    let(:cluster_params) { { cluster_id: cluster.id } }

    let(:cluster) do
      create(:cluster, :project, :provided_by_gcp,
             projects: [project])
    end

    context 'non-authorized user' do
      it 'responds with 404' do
        delete api("/projects/#{project.id}/clusters/#{cluster.id}", non_member), params: cluster_params

        expect(response).to have_gitlab_http_status(404)
      end
    end

    context 'authorized user' do
      before do
        delete api("/projects/#{project.id}/clusters/#{cluster.id}", current_user), params: cluster_params
      end

      it 'responds with 204' do
        expect(response).to have_gitlab_http_status(204)
      end

      it 'deletes the cluster' do
        expect(Clusters::Cluster.exists?(id: cluster.id)).to be_falsy
      end

      context 'with a cluster that does not belong to user' do
        let(:cluster) { create(:cluster, :project, :provided_by_user) }

        it 'responds with 404' do
          expect(response).to have_gitlab_http_status(404)
        end
      end
    end
  end
end
