Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-03-21 15:08:46 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-03-21 15:08:46 +0300
commit7f521d27811b472c43203ed3d1bde4460a617f89 (patch)
tree47f1a10b776991e86c6db002bc6e03e83acc356a /spec/tooling/lib
parent83e3316a189d3b709b23af30647b5f9ea5377bac (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/tooling/lib')
-rw-r--r--spec/tooling/lib/tooling/kubernetes_client_spec.rb483
-rw-r--r--spec/tooling/lib/tooling/mappings/partial_to_views_mappings_spec.rb274
2 files changed, 559 insertions, 198 deletions
diff --git a/spec/tooling/lib/tooling/kubernetes_client_spec.rb b/spec/tooling/lib/tooling/kubernetes_client_spec.rb
index 50d33182a42..20eb78c2f4f 100644
--- a/spec/tooling/lib/tooling/kubernetes_client_spec.rb
+++ b/spec/tooling/lib/tooling/kubernetes_client_spec.rb
@@ -1,286 +1,373 @@
# frozen_string_literal: true
+require 'time'
require_relative '../../../../tooling/lib/tooling/kubernetes_client'
RSpec.describe Tooling::KubernetesClient do
- let(:namespace) { 'review-apps' }
- let(:release_name) { 'my-release' }
- let(:pod_for_release) { "pod-my-release-abcd" }
- let(:raw_resource_names_str) { "NAME\nfoo\n#{pod_for_release}\nbar" }
- let(:raw_resource_names) { raw_resource_names_str.lines.map(&:strip) }
-
- subject { described_class.new(namespace: namespace) }
+ let(:instance) { described_class.new }
+ let(:one_day_ago) { Time.now - 3600 * 24 * 1 }
+ let(:two_days_ago) { Time.now - 3600 * 24 * 2 }
+ let(:three_days_ago) { Time.now - 3600 * 24 * 3 }
+
+ before do
+ # Global mock to ensure that no kubectl commands are run by accident in a test.
+ allow(instance).to receive(:run_command)
+ end
- describe 'RESOURCE_LIST' do
- it 'returns the correct list of resources separated by commas' do
- expect(described_class::RESOURCE_LIST).to eq('ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa,crd')
+ describe '#cleanup_pvcs_by_created_at' do
+ let(:pvc_1_created_at) { three_days_ago }
+ let(:pvc_2_created_at) { three_days_ago }
+ let(:pvc_1_namespace) { 'review-first-review-app' }
+ let(:pvc_2_namespace) { 'review-second-review-app' }
+ let(:kubectl_pvcs_json) do
+ <<~JSON
+ {
+ "apiVersion": "v1",
+ "items": [
+ {
+ "apiVersion": "v1",
+ "kind": "PersistentVolumeClaim",
+ "metadata": {
+ "creationTimestamp": "#{pvc_1_created_at.utc.iso8601}",
+ "name": "pvc1",
+ "namespace": "#{pvc_1_namespace}"
+ }
+ },
+ {
+ "apiVersion": "v1",
+ "kind": "PersistentVolumeClaim",
+ "metadata": {
+ "creationTimestamp": "#{pvc_2_created_at.utc.iso8601}",
+ "name": "pvc2",
+ "namespace": "#{pvc_2_namespace}"
+ }
+ }
+ ]
+ }
+ JSON
end
- end
- describe '#cleanup_by_release' do
+ subject { instance.cleanup_pvcs_by_created_at(created_before: two_days_ago) }
+
before do
- allow(subject).to receive(:raw_resource_names).and_return(raw_resource_names)
+ allow(instance).to receive(:run_command).with(
+ "kubectl get pvc --all-namespaces --sort-by='{.metadata.creationTimestamp}' -o json"
+ ).and_return(kubectl_pvcs_json)
end
- shared_examples 'a kubectl command to delete resources' do
- let(:wait) { true }
- let(:release_names_in_command) { release_name.respond_to?(:join) ? %(-l 'release in (#{release_name.join(', ')})') : %(-l release="#{release_name}") }
-
- specify do
- expect(Gitlab::Popen).to receive(:popen_with_detail)
- .with(["kubectl delete #{described_class::RESOURCE_LIST} " +
- %(--namespace "#{namespace}" --now --ignore-not-found --wait=#{wait} #{release_names_in_command})])
- .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true)))
+ context 'when no pvcs are stale' do
+ let(:pvc_1_created_at) { one_day_ago }
+ let(:pvc_2_created_at) { one_day_ago }
- expect(Gitlab::Popen).to receive(:popen_with_detail)
- .with([%(kubectl delete --namespace "#{namespace}" --ignore-not-found #{pod_for_release})])
- .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true)))
+ it 'does not delete any PVC' do
+ expect(instance).not_to receive(:run_command).with(/kubectl delete pvc/)
- # We're not verifying the output here, just silencing it
- expect { subject.cleanup_by_release(release_name: release_name) }.to output.to_stdout
+ subject
end
end
- it 'raises an error if the Kubernetes command fails' do
- expect(Gitlab::Popen).to receive(:popen_with_detail)
- .with(["kubectl delete #{described_class::RESOURCE_LIST} " +
- %(--namespace "#{namespace}" --now --ignore-not-found --wait=true -l release="#{release_name}")])
- .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false)))
+ context 'when some pvcs are stale' do
+ let(:pvc_1_created_at) { three_days_ago }
+ let(:pvc_2_created_at) { three_days_ago }
- expect { subject.cleanup_by_release(release_name: release_name) }.to raise_error(described_class::CommandFailedError)
- end
+ context 'when some pvcs are not in a review app namespaces' do
+ let(:pvc_1_namespace) { 'review-my-review-app' }
+ let(:pvc_2_namespace) { 'review-apps' } # This is not a review apps namespace, so we should not delete PVCs inside it
- it_behaves_like 'a kubectl command to delete resources'
+ it 'deletes the stale pvcs inside of review-apps namespaces only' do
+ expect(instance).to receive(:run_command).with("kubectl delete pvc --namespace=#{pvc_1_namespace} --now --ignore-not-found pvc1")
+ expect(instance).not_to receive(:run_command).with(/kubectl delete pvc --namespace=#{pvc_2_namespace}/)
- context 'with multiple releases' do
- let(:release_name) { %w[my-release my-release-2] }
+ subject
+ end
+ end
- it_behaves_like 'a kubectl command to delete resources'
- end
+ context 'when all pvcs are in review-apps namespaces' do
+ let(:pvc_1_namespace) { 'review-my-review-app' }
+ let(:pvc_2_namespace) { 'review-another-review-app' }
- context 'with `wait: false`' do
- let(:wait) { false }
+ it 'deletes all of the stale pvcs' do
+ expect(instance).to receive(:run_command).with("kubectl delete pvc --namespace=#{pvc_1_namespace} --now --ignore-not-found pvc1")
+ expect(instance).to receive(:run_command).with("kubectl delete pvc --namespace=#{pvc_2_namespace} --now --ignore-not-found pvc2")
- it_behaves_like 'a kubectl command to delete resources'
+ subject
+ end
+ end
end
end
- describe '#cleanup_by_created_at' do
- let(:two_days_ago) { Time.now - 3600 * 24 * 2 }
- let(:resource_type) { 'pvc' }
- let(:resource_names) { [pod_for_release] }
+ describe '#cleanup_namespaces_by_created_at' do
+ let(:namespace_1_created_at) { three_days_ago }
+ let(:namespace_2_created_at) { three_days_ago }
+ let(:namespace_1_name) { 'review-first-review-app' }
+ let(:namespace_2_name) { 'review-second-review-app' }
+ let(:kubectl_namespaces_json) do
+ <<~JSON
+ {
+ "apiVersion": "v1",
+ "items": [
+ {
+ "apiVersion": "v1",
+ "kind": "namespace",
+ "metadata": {
+ "creationTimestamp": "#{namespace_1_created_at.utc.iso8601}",
+ "name": "#{namespace_1_name}"
+ }
+ },
+ {
+ "apiVersion": "v1",
+ "kind": "namespace",
+ "metadata": {
+ "creationTimestamp": "#{namespace_2_created_at.utc.iso8601}",
+ "name": "#{namespace_2_name}"
+ }
+ }
+ ]
+ }
+ JSON
+ end
+
+ subject { instance.cleanup_namespaces_by_created_at(created_before: two_days_ago) }
before do
- allow(subject).to receive(:resource_names_created_before).with(resource_type: resource_type, created_before: two_days_ago).and_return(resource_names)
+ allow(instance).to receive(:run_command).with(
+ "kubectl get namespace --all-namespaces --sort-by='{.metadata.creationTimestamp}' -o json"
+ ).and_return(kubectl_namespaces_json)
end
- shared_examples 'a kubectl command to delete resources by older than given creation time' do
- let(:wait) { true }
- let(:release_names_in_command) { resource_names.join(' ') }
+ context 'when no namespaces are stale' do
+ let(:namespace_1_created_at) { one_day_ago }
+ let(:namespace_2_created_at) { one_day_ago }
- specify do
- expect(Gitlab::Popen).to receive(:popen_with_detail)
- .with(["kubectl delete #{resource_type} ".squeeze(' ') +
- %(--namespace "#{namespace}" --now --ignore-not-found --wait=#{wait} #{release_names_in_command})])
- .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true)))
+ it 'does not delete any namespace' do
+ expect(instance).not_to receive(:run_command).with(/kubectl delete namespace/)
- # We're not verifying the output here, just silencing it
- expect { subject.cleanup_by_created_at(resource_type: resource_type, created_before: two_days_ago) }.to output.to_stdout
+ subject
end
end
- it 'raises an error if the Kubernetes command fails' do
- expect(Gitlab::Popen).to receive(:popen_with_detail)
- .with(["kubectl delete #{resource_type} " +
- %(--namespace "#{namespace}" --now --ignore-not-found --wait=true #{pod_for_release})])
- .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false)))
+ context 'when some namespaces are stale' do
+ let(:namespace_1_created_at) { three_days_ago }
+ let(:namespace_2_created_at) { three_days_ago }
- expect { subject.cleanup_by_created_at(resource_type: resource_type, created_before: two_days_ago) }.to raise_error(described_class::CommandFailedError)
- end
+ context 'when some namespaces are not review app namespaces' do
+ let(:namespace_1_name) { 'review-my-review-app' }
+ let(:namespace_2_name) { 'review-apps' } # This is not a review apps namespace, so we should not try to delete it
- it_behaves_like 'a kubectl command to delete resources by older than given creation time'
+ it 'only deletes the review app namespaces' do
+ expect(instance).to receive(:run_command).with("kubectl delete namespace --now --ignore-not-found #{namespace_1_name}")
- context 'with multiple resource names' do
- let(:resource_names) { %w[pod-1 pod-2] }
+ subject
+ end
+ end
- it_behaves_like 'a kubectl command to delete resources by older than given creation time'
- end
+ context 'when all namespaces are review app namespaces' do
+ let(:namespace_1_name) { 'review-my-review-app' }
+ let(:namespace_2_name) { 'review-another-review-app' }
- context 'with `wait: false`' do
- let(:wait) { false }
+ it 'deletes all of the stale namespaces' do
+ expect(instance).to receive(:run_command).with("kubectl delete namespace --now --ignore-not-found #{namespace_1_name} #{namespace_2_name}")
- it_behaves_like 'a kubectl command to delete resources by older than given creation time'
+ subject
+ end
+ end
end
+ end
- context 'with no resource_type given' do
- let(:resource_type) { nil }
+ describe '#delete_pvc' do
+ let(:pvc_name) { 'my-pvc' }
- it_behaves_like 'a kubectl command to delete resources by older than given creation time'
- end
+ subject { instance.delete_pvc(pvc_name, pvc_namespace) }
+
+ context 'when the namespace is not a review app namespace' do
+ let(:pvc_namespace) { 'not-a-review-app-namespace' }
- context 'with multiple resource_type given' do
- let(:resource_type) { 'pvc,service' }
+ it 'does not delete the pvc' do
+ expect(instance).not_to receive(:run_command).with(/kubectl delete pvc/)
- it_behaves_like 'a kubectl command to delete resources by older than given creation time'
+ subject
+ end
end
- context 'with no resources found' do
- let(:resource_names) { [] }
+ context 'when the namespace is a review app namespace' do
+ let(:pvc_namespace) { 'review-apple-test' }
- it 'does not call #delete_by_exact_names' do
- expect(subject).not_to receive(:delete_by_exact_names)
+ it 'deletes the pvc' do
+ expect(instance).to receive(:run_command).with("kubectl delete pvc --namespace=#{pvc_namespace} --now --ignore-not-found #{pvc_name}")
- subject.cleanup_by_created_at(resource_type: resource_type, created_before: two_days_ago)
+ subject
end
end
end
- describe '#cleanup_review_app_namespaces' do
- let(:two_days_ago) { Time.now - 3600 * 24 * 2 }
- let(:namespaces) { %w[review-abc-123 review-xyz-789] }
+ describe '#delete_namespaces' do
+ subject { instance.delete_namespaces(namespaces) }
- subject { described_class.new(namespace: nil) }
+ context 'when at least one namespace is not a review app namespace' do
+ let(:namespaces) { %w[review-ns-1 default] }
- before do
- allow(subject).to receive(:review_app_namespaces_created_before).with(created_before: two_days_ago).and_return(namespaces)
+ it 'does not delete any namespace' do
+ expect(instance).not_to receive(:run_command).with(/kubectl delete namespace/)
+
+ subject
+ end
end
- shared_examples 'a kubectl command to delete namespaces older than given creation time' do
- let(:wait) { true }
+ context 'when all namespaces are review app namespaces' do
+ let(:namespaces) { %w[review-ns-1 review-ns-2] }
- specify do
- expect(Gitlab::Popen).to receive(:popen_with_detail)
- .with(["kubectl delete namespace " +
- %(--now --ignore-not-found --wait=#{wait} #{namespaces.join(' ')})])
- .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: true)))
+ it 'deletes the namespaces' do
+ expect(instance).to receive(:run_command).with("kubectl delete namespace --now --ignore-not-found #{namespaces.join(' ')}")
- # We're not verifying the output here, just silencing it
- expect { subject.cleanup_review_app_namespaces(created_before: two_days_ago) }.to output.to_stdout
+ subject
end
end
+ end
- it_behaves_like 'a kubectl command to delete namespaces older than given creation time'
-
- it 'raises an error if the Kubernetes command fails' do
- expect(Gitlab::Popen).to receive(:popen_with_detail)
- .with(["kubectl delete namespace " +
- %(--now --ignore-not-found --wait=true #{namespaces.join(' ')})])
- .and_return(Gitlab::Popen::Result.new([], '', '', double(success?: false)))
-
- expect { subject.cleanup_review_app_namespaces(created_before: two_days_ago) }.to raise_error(described_class::CommandFailedError)
+ describe '#pvcs_created_before' do
+ subject { instance.pvcs_created_before(created_before: two_days_ago) }
+
+ let(:pvc_1_created_at) { three_days_ago }
+ let(:pvc_2_created_at) { three_days_ago }
+ let(:pvc_1_namespace) { 'review-first-review-app' }
+ let(:pvc_2_namespace) { 'review-second-review-app' }
+ let(:kubectl_pvcs_json) do
+ <<~JSON
+ {
+ "apiVersion": "v1",
+ "items": [
+ {
+ "apiVersion": "v1",
+ "kind": "PersistentVolumeClaim",
+ "metadata": {
+ "creationTimestamp": "#{pvc_1_created_at.utc.iso8601}",
+ "name": "pvc1",
+ "namespace": "#{pvc_1_namespace}"
+ }
+ },
+ {
+ "apiVersion": "v1",
+ "kind": "PersistentVolumeClaim",
+ "metadata": {
+ "creationTimestamp": "#{pvc_2_created_at.utc.iso8601}",
+ "name": "pvc2",
+ "namespace": "#{pvc_2_namespace}"
+ }
+ }
+ ]
+ }
+ JSON
end
- context 'with no namespaces found' do
- let(:namespaces) { [] }
+ it 'calls #resource_created_before with the correct parameters' do
+ expect(instance).to receive(:resource_created_before).with(resource_type: 'pvc', created_before: two_days_ago)
- it 'does not call #delete_namespaces_by_exact_names' do
- expect(subject).not_to receive(:delete_namespaces_by_exact_names)
+ subject
+ end
- subject.cleanup_review_app_namespaces(created_before: two_days_ago)
- end
+ it 'returns a hash with two keys' do
+ allow(instance).to receive(:run_command).with(
+ "kubectl get pvc --all-namespaces --sort-by='{.metadata.creationTimestamp}' -o json"
+ ).and_return(kubectl_pvcs_json)
+
+ expect(subject).to match_array([
+ {
+ resource_name: 'pvc1',
+ namespace: 'review-first-review-app'
+ },
+ {
+ resource_name: 'pvc2',
+ namespace: 'review-second-review-app'
+ }
+ ])
end
end
- describe '#raw_resource_names' do
- it 'calls kubectl to retrieve the resource names' do
- expect(Gitlab::Popen).to receive(:popen_with_detail)
- .with(["kubectl get #{described_class::RESOURCE_LIST} " +
- %(--namespace "#{namespace}" -o name)])
- .and_return(Gitlab::Popen::Result.new([], raw_resource_names_str, '', double(success?: true)))
-
- expect(subject.__send__(:raw_resource_names)).to eq(raw_resource_names)
+ describe '#namespaces_created_before' do
+ subject { instance.namespaces_created_before(created_before: two_days_ago) }
+
+ let(:namespace_1_created_at) { three_days_ago }
+ let(:namespace_2_created_at) { three_days_ago }
+ let(:namespace_1_name) { 'review-first-review-app' }
+ let(:namespace_2_name) { 'review-second-review-app' }
+ let(:kubectl_namespaces_json) do
+ <<~JSON
+ {
+ "apiVersion": "v1",
+ "items": [
+ {
+ "apiVersion": "v1",
+ "kind": "namespace",
+ "metadata": {
+ "creationTimestamp": "#{namespace_1_created_at.utc.iso8601}",
+ "name": "#{namespace_1_name}"
+ }
+ },
+ {
+ "apiVersion": "v1",
+ "kind": "namespace",
+ "metadata": {
+ "creationTimestamp": "#{namespace_2_created_at.utc.iso8601}",
+ "name": "#{namespace_2_name}"
+ }
+ }
+ ]
+ }
+ JSON
end
- end
- describe '#resource_names_created_before' do
- let(:three_days_ago) { Time.now - 3600 * 24 * 3 }
- let(:two_days_ago) { Time.now - 3600 * 24 * 2 }
- let(:pvc_created_three_days_ago) { 'pvc-created-three-days-ago' }
- let(:resource_type) { 'pvc' }
- let(:raw_resources) do
- {
- items: [
- {
- apiVersion: "v1",
- kind: "PersistentVolumeClaim",
- metadata: {
- creationTimestamp: three_days_ago,
- name: pvc_created_three_days_ago
- }
- },
- {
- apiVersion: "v1",
- kind: "PersistentVolumeClaim",
- metadata: {
- creationTimestamp: Time.now,
- name: 'another-pvc'
- }
- }
- ]
- }.to_json
+ it 'calls #resource_created_before with the correct parameters' do
+ expect(instance).to receive(:resource_created_before).with(resource_type: 'namespace', created_before: two_days_ago)
+
+ subject
end
- shared_examples 'a kubectl command to retrieve resource names sorted by creationTimestamp' do
- specify do
- expect(Gitlab::Popen).to receive(:popen_with_detail)
- .with(["kubectl get #{resource_type} ".squeeze(' ') +
- %(--namespace "#{namespace}" ) +
- "--sort-by='{.metadata.creationTimestamp}' -o json"])
- .and_return(Gitlab::Popen::Result.new([], raw_resources, '', double(success?: true)))
+ it 'returns an array of namespaces' do
+ allow(instance).to receive(:run_command).with(
+ "kubectl get namespace --all-namespaces --sort-by='{.metadata.creationTimestamp}' -o json"
+ ).and_return(kubectl_namespaces_json)
- expect(subject.__send__(:resource_names_created_before, resource_type: resource_type, created_before: two_days_ago)).to contain_exactly(pvc_created_three_days_ago)
- end
+ expect(subject).to match_array(%w[review-first-review-app review-second-review-app])
end
+ end
- it_behaves_like 'a kubectl command to retrieve resource names sorted by creationTimestamp'
+ describe '#run_command' do
+ subject { instance.run_command(command) }
- context 'with no resource_type given' do
- let(:resource_type) { nil }
+ before do
+ # We undo the global mock just for this method
+ allow(instance).to receive(:run_command).and_call_original
- it_behaves_like 'a kubectl command to retrieve resource names sorted by creationTimestamp'
+ # Mock stdout
+ allow(instance).to receive(:puts)
end
- context 'with multiple resource_type given' do
- let(:resource_type) { 'pvc,service' }
+ context 'when executing a successful command' do
+ let(:command) { 'true' } # https://linux.die.net/man/1/true
- it_behaves_like 'a kubectl command to retrieve resource names sorted by creationTimestamp'
- end
- end
+ it 'displays the name of the command to stdout' do
+ expect(instance).to receive(:puts).with("Running command: `#{command}`")
+
+ subject
+ end
- describe '#review_app_namespaces_created_before' do
- let(:three_days_ago) { Time.now - 3600 * 24 * 3 }
- let(:two_days_ago) { Time.now - 3600 * 24 * 2 }
- let(:namespace_created_three_days_ago) { 'review-ns-created-three-days-ago' }
- let(:resource_type) { 'namespace' }
- let(:raw_resources) do
- {
- items: [
- {
- apiVersion: "v1",
- kind: "Namespace",
- metadata: {
- creationTimestamp: three_days_ago,
- name: namespace_created_three_days_ago
- }
- },
- {
- apiVersion: "v1",
- kind: "Namespace",
- metadata: {
- creationTimestamp: Time.now,
- name: 'another-namespace'
- }
- }
- ]
- }.to_json
+ it 'does not raise an error' do
+ expect { subject }.not_to raise_error
+ end
end
- specify do
- expect(Gitlab::Popen).to receive(:popen_with_detail)
- .with(["kubectl get namespace --sort-by='{.metadata.creationTimestamp}' -o json"])
- .and_return(Gitlab::Popen::Result.new([], raw_resources, '', double(success?: true)))
+ context 'when executing an unsuccessful command' do
+ let(:command) { 'false' } # https://linux.die.net/man/1/false
+
+ it 'displays the name of the command to stdout' do
+ expect(instance).to receive(:puts).with("Running command: `#{command}`")
- expect(subject.__send__(:review_app_namespaces_created_before, created_before: two_days_ago)).to eq([namespace_created_three_days_ago])
+ expect { subject }.to raise_error(described_class::CommandFailedError)
+ end
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(described_class::CommandFailedError)
+ end
end
end
end
diff --git a/spec/tooling/lib/tooling/mappings/partial_to_views_mappings_spec.rb b/spec/tooling/lib/tooling/mappings/partial_to_views_mappings_spec.rb
new file mode 100644
index 00000000000..69dddb0ae3d
--- /dev/null
+++ b/spec/tooling/lib/tooling/mappings/partial_to_views_mappings_spec.rb
@@ -0,0 +1,274 @@
+# frozen_string_literal: true
+
+require 'tempfile'
+require 'fileutils'
+require_relative '../../../../../tooling/lib/tooling/mappings/partial_to_views_mappings'
+
+RSpec.describe Tooling::Mappings::PartialToViewsMappings, feature_category: :tooling do
+ attr_accessor :view_base_folder, :changes_file, :output_file
+
+ let(:instance) { described_class.new(changes_file, output_file, view_base_folder: view_base_folder) }
+ let(:changes_file_content) { "changed_file1 changed_file2" }
+ let(:output_file_content) { "previously_added_view.html.haml" }
+
+ around do |example|
+ self.changes_file = Tempfile.new('changes')
+ self.output_file = Tempfile.new('output_file')
+
+ # See https://ruby-doc.org/stdlib-1.9.3/libdoc/tempfile/rdoc/
+ # Tempfile.html#class-Tempfile-label-Explicit+close
+ begin
+ Dir.mktmpdir do |tmp_views_base_folder|
+ self.view_base_folder = tmp_views_base_folder
+ example.run
+ end
+ ensure
+ changes_file.close
+ output_file.close
+ changes_file.unlink
+ output_file.unlink
+ end
+ end
+
+ before do
+ # We write into the temp files initially, to check how the code modified those files
+ File.write(changes_file, changes_file_content)
+ File.write(output_file, output_file_content)
+ end
+
+ describe '#execute' do
+ subject { instance.execute }
+
+ let(:changed_files) { ["#{view_base_folder}/my_view.html.haml"] }
+ let(:changes_file_content) { changed_files.join(" ") }
+
+ before do
+ # We create all of the changed_files, so that they are part of the filtered files
+ changed_files.each { |changed_file| FileUtils.touch(changed_file) }
+ end
+
+ it 'does not modify the content of the input file' do
+ expect { subject }.not_to change { File.read(changes_file) }
+ end
+
+ context 'when no partials were modified' do
+ it 'empties the output file' do
+ expect { subject }.to change { File.read(output_file) }.from(output_file_content).to('')
+ end
+ end
+
+ context 'when some partials were modified' do
+ let(:changed_files) do
+ [
+ "#{view_base_folder}/my_view.html.haml",
+ "#{view_base_folder}/_my_partial.html.haml"
+ ]
+ end
+
+ before do
+ # We create a red-herring partial to have a more convincing test suite
+ FileUtils.touch("#{view_base_folder}/_another_partial.html.haml")
+ end
+
+ context 'when the partials are not included in any views' do
+ before do
+ File.write("#{view_base_folder}/my_view.html.haml", "render 'another_partial'")
+ end
+
+ it 'empties the output file' do
+ expect { subject }.to change { File.read(output_file) }.from(output_file_content).to('')
+ end
+ end
+
+ context 'when the partials are included in views' do
+ before do
+ File.write("#{view_base_folder}/my_view.html.haml", "render 'my_partial'")
+ end
+
+ it 'writes the view including the partial to the output' do
+ expect { subject }.to change { File.read(output_file) }
+ .from(output_file_content)
+ .to("#{view_base_folder}/my_view.html.haml")
+ end
+ end
+ end
+ end
+
+ describe '#filter_files' do
+ subject { instance.filter_files }
+
+ let(:changes_file_content) { file_path }
+
+ context 'when the file does not exist on disk' do
+ let(:file_path) { "#{view_base_folder}/_index.html.erb" }
+
+ it 'returns an empty array' do
+ expect(subject).to be_empty
+ end
+ end
+
+ context 'when the file exists on disk' do
+ before do
+ File.write(file_path, "I am a partial!")
+ end
+
+ context 'when the file is not in the view base folders' do
+ let(:file_path) { "/tmp/_index.html.haml" }
+
+ it 'returns an empty array' do
+ expect(subject).to be_empty
+ end
+ end
+
+ context 'when the filename does not start with an underscore' do
+ let(:file_path) { "#{view_base_folder}/index.html.haml" }
+
+ it 'returns an empty array' do
+ expect(subject).to be_empty
+ end
+ end
+
+ context 'when the filename does not have the correct extension' do
+ let(:file_path) { "#{view_base_folder}/_index.html.erb" }
+
+ it 'returns an empty array' do
+ expect(subject).to be_empty
+ end
+ end
+
+ context 'when the file is a partial' do
+ let(:file_path) { "#{view_base_folder}/_index.html.haml" }
+
+ it 'returns the file' do
+ expect(subject).to match_array(file_path)
+ end
+ end
+ end
+ end
+
+ describe '#extract_partial_keyword' do
+ subject { instance.extract_partial_keyword('ee/app/views/shared/_new_project_item_vue_select.html.haml') }
+
+ it 'returns the correct partial keyword' do
+ expect(subject).to eq('new_project_item_vue_select')
+ end
+ end
+
+ describe '#view_includes_modified_partial?' do
+ subject { instance.view_includes_modified_partial?(view_file, included_partial_name) }
+
+ context 'when the included partial name is relative to the view file' do
+ let(:view_file) { "#{view_base_folder}/components/my_view.html.haml" }
+ let(:included_partial_name) { 'subfolder/relative_partial' }
+
+ before do
+ FileUtils.mkdir_p("#{view_base_folder}/components/subfolder")
+ File.write(changes_file_content, "I am a partial!")
+ end
+
+ context 'when the partial is not part of the changed files' do
+ let(:changes_file_content) { "#{view_base_folder}/components/subfolder/_not_the_partial.html.haml" }
+
+ it 'returns false' do
+ expect(subject).to be_falsey
+ end
+ end
+
+ context 'when the partial is part of the changed files' do
+ let(:changes_file_content) { "#{view_base_folder}/components/subfolder/_relative_partial.html.haml" }
+
+ it 'returns true' do
+ expect(subject).to be_truthy
+ end
+ end
+ end
+
+ context 'when the included partial name is relative to the base views folder' do
+ let(:view_file) { "#{view_base_folder}/components/my_view.html.haml" }
+ let(:included_partial_name) { 'shared/absolute_partial' }
+
+ before do
+ FileUtils.mkdir_p("#{view_base_folder}/components")
+ FileUtils.mkdir_p("#{view_base_folder}/shared")
+ File.write(changes_file_content, "I am a partial!")
+ end
+
+ context 'when the partial is not part of the changed files' do
+ let(:changes_file_content) { "#{view_base_folder}/shared/not_the_partial" }
+
+ it 'returns false' do
+ expect(subject).to be_falsey
+ end
+ end
+
+ context 'when the partial is part of the changed files' do
+ let(:changes_file_content) { "#{view_base_folder}/shared/_absolute_partial.html.haml" }
+
+ it 'returns true' do
+ expect(subject).to be_truthy
+ end
+ end
+ end
+ end
+
+ describe '#reconstruct_partial_filename' do
+ subject { instance.reconstruct_partial_filename(partial_name) }
+
+ context 'when the partial does not contain a path' do
+ let(:partial_name) { 'sidebar' }
+
+ it 'returns the correct filename' do
+ expect(subject).to eq('_sidebar.html.haml')
+ end
+ end
+
+ context 'when the partial contains a path' do
+ let(:partial_name) { 'shared/components/sidebar' }
+
+ it 'returns the correct filename' do
+ expect(subject).to eq('shared/components/_sidebar.html.haml')
+ end
+ end
+ end
+
+ describe '#find_pattern_in_file' do
+ let(:subject) { instance.find_pattern_in_file(file.path, /pattern/) }
+ let(:file) { Tempfile.new('find_pattern_in_file') }
+
+ before do
+ file.write(file_content)
+ file.close
+ end
+
+ context 'when the file contains the pattern' do
+ let(:file_content) do
+ <<~FILE
+ Beginning of file
+
+ pattern
+ pattern
+ pattern
+
+ End of file
+ FILE
+ end
+
+ it 'returns the pattern once' do
+ expect(subject).to match_array(%w[pattern])
+ end
+ end
+
+ context 'when the file does not contain the pattern' do
+ let(:file_content) do
+ <<~FILE
+ Beginning of file
+ End of file
+ FILE
+ end
+
+ it 'returns an empty array' do
+ expect(subject).to match_array([])
+ end
+ end
+ end
+end