From 3aa1264dc6c0de3625bb1a2d6a0ee90140a2f519 Mon Sep 17 00:00:00 2001
From: Douwe Maan <douwe@selenight.nl>
Date: Sun, 29 Jan 2017 22:01:31 -0600
Subject: [PATCH] Add tests

---
 app/models/ci/pipeline.rb                     |   2 +-
 app/models/project.rb                         |   6 +-
 app/models/repository.rb                      |   4 +-
 lib/gitlab/route_map.rb                       |   2 +
 .../projects/blobs/view_on_env_spec.rb        |   7 +
 .../{commits => commit}/cherry_pick_spec.rb   |   0
 .../projects/commit/view_on_env_spec.rb       |   9 ++
 .../projects/compare/view_on_env_spec.rb      |   9 ++
 spec/features/{ => projects}/compare_spec.rb  |   0
 .../merge_requests/view_on_env_spec.rb        |   9 ++
 spec/helpers/commits_helper_spec.rb           |  19 +++
 spec/lib/gitlab/route_map_spec.rb             |  89 +++++++++++++
 spec/models/environment_spec.rb               |  48 +++++++
 spec/models/merge_request_spec.rb             |  24 ++++
 spec/models/project_spec.rb                   | 123 ++++++++++++++++++
 spec/models/repository_spec.rb                |  36 +++++
 16 files changed, 381 insertions(+), 6 deletions(-)
 create mode 100644 spec/features/projects/blobs/view_on_env_spec.rb
 rename spec/features/projects/{commits => commit}/cherry_pick_spec.rb (100%)
 create mode 100644 spec/features/projects/commit/view_on_env_spec.rb
 create mode 100644 spec/features/projects/compare/view_on_env_spec.rb
 rename spec/features/{ => projects}/compare_spec.rb (100%)
 create mode 100644 spec/features/projects/merge_requests/view_on_env_spec.rb
 create mode 100644 spec/lib/gitlab/route_map_spec.rb

diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 8db53ea56dd..70bb4a224a1 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -283,7 +283,7 @@ module Ci
     def ci_yaml_file
       return @ci_yaml_file if defined?(@ci_yaml_file)
 
-      @ci_yaml_file ||= project.repository.ci_yaml_file(sha)
+      @ci_yaml_file ||= project.repository.gitlab_ci_yml_for(sha)
     end
 
     def has_yaml_errors?
diff --git a/app/models/project.rb b/app/models/project.rb
index 42a79557136..15c6e25e73f 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -1342,10 +1342,10 @@ class Project < ActiveRecord::Base
     end
   end
 
-  def route_map_for_commit(commit_sha)
+  def route_map_for(commit_sha)
     @route_maps_by_commit ||= Hash.new do |h, sha|
       h[sha] = begin
-        data = repository.route_map_file(sha)
+        data = repository.route_map_for(sha)
         next unless data
 
         Gitlab::RouteMap.new(data)
@@ -1358,7 +1358,7 @@ class Project < ActiveRecord::Base
   end
 
   def public_path_for_source_path(path, commit_sha)
-    map = route_map_for_commit(commit_sha)
+    map = route_map_for(commit_sha)
     return unless map
 
     map.public_path_for_source_path(path)
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 9aa0cc250f0..ba9c038b66d 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -1187,14 +1187,14 @@ class Repository
     end
   end
 
-  def route_map_file(sha)
+  def route_map_for(sha)
     blob = blob_at(sha, ROUTE_MAP_PATH)
     return unless blob
     blob.load_all_data!(self)
     blob.data
   end
 
-  def ci_yaml_file(sha)
+  def gitlab_ci_yml_for(sha)
     blob = blob_at(sha, GITLAB_CI_YML_PATH)
     return unless blob
     blob.load_all_data!(self)
diff --git a/lib/gitlab/route_map.rb b/lib/gitlab/route_map.rb
index 89985d90c10..b1a93d44a45 100644
--- a/lib/gitlab/route_map.rb
+++ b/lib/gitlab/route_map.rb
@@ -2,6 +2,8 @@ module Gitlab
   class RouteMap
     class FormatError < StandardError; end
 
+    attr_reader :map
+
     def initialize(data)
       begin
         entries = YAML.safe_load(data)
diff --git a/spec/features/projects/blobs/view_on_env_spec.rb b/spec/features/projects/blobs/view_on_env_spec.rb
new file mode 100644
index 00000000000..360f9d66609
--- /dev/null
+++ b/spec/features/projects/blobs/view_on_env_spec.rb
@@ -0,0 +1,7 @@
+require 'spec_helper'
+
+feature 'Blob' do
+  describe 'View on environment' do
+    # TODO: Test
+  end
+end
diff --git a/spec/features/projects/commits/cherry_pick_spec.rb b/spec/features/projects/commit/cherry_pick_spec.rb
similarity index 100%
rename from spec/features/projects/commits/cherry_pick_spec.rb
rename to spec/features/projects/commit/cherry_pick_spec.rb
diff --git a/spec/features/projects/commit/view_on_env_spec.rb b/spec/features/projects/commit/view_on_env_spec.rb
new file mode 100644
index 00000000000..eee2814089a
--- /dev/null
+++ b/spec/features/projects/commit/view_on_env_spec.rb
@@ -0,0 +1,9 @@
+require 'spec_helper'
+
+feature 'Commit' do
+  describe 'Diff' do
+    describe 'View on environment' do
+      # TODO: Test
+    end
+  end
+end
diff --git a/spec/features/projects/compare/view_on_env_spec.rb b/spec/features/projects/compare/view_on_env_spec.rb
new file mode 100644
index 00000000000..0a4ec03b17f
--- /dev/null
+++ b/spec/features/projects/compare/view_on_env_spec.rb
@@ -0,0 +1,9 @@
+require 'spec_helper'
+
+feature 'Compare' do
+  describe 'Diff' do
+    describe 'View on environment' do
+      # TODO: Test
+    end
+  end
+end
diff --git a/spec/features/compare_spec.rb b/spec/features/projects/compare_spec.rb
similarity index 100%
rename from spec/features/compare_spec.rb
rename to spec/features/projects/compare_spec.rb
diff --git a/spec/features/projects/merge_requests/view_on_env_spec.rb b/spec/features/projects/merge_requests/view_on_env_spec.rb
new file mode 100644
index 00000000000..b25e499acd6
--- /dev/null
+++ b/spec/features/projects/merge_requests/view_on_env_spec.rb
@@ -0,0 +1,9 @@
+require 'spec_helper'
+
+feature 'Merge Request' do
+  describe 'Diff' do
+    describe 'View on environment' do
+      # TODO: Test
+    end
+  end
+end
diff --git a/spec/helpers/commits_helper_spec.rb b/spec/helpers/commits_helper_spec.rb
index 727c25ff529..85c663030d8 100644
--- a/spec/helpers/commits_helper_spec.rb
+++ b/spec/helpers/commits_helper_spec.rb
@@ -26,4 +26,23 @@ describe CommitsHelper do
         not_to include('onmouseover="alert(1)"')
     end
   end
+
+  describe '#view_on_environment_btn' do
+    let(:project) { create(:empty_project) }
+    let(:environment) { create(:environment, external_url: 'http://example.com') }
+    let(:path) { 'source/file.html' }
+    let(:sha) { RepoHelpers.sample_commit.id }
+
+    before do
+      allow(environment).to receive(:external_url_for).with(path, sha).and_return('http://example.com/file.html')
+    end
+
+    it 'returns a link tag linking to the file in the environment' do
+      html = helper.view_on_environment_btn(sha, path, environment)
+      node = Nokogiri::HTML.parse(html).at_css('a')
+
+      expect(node[:title]).to eq('View on example.com')
+      expect(node[:href]).to eq('http://example.com/file.html')
+    end
+  end
 end
diff --git a/spec/lib/gitlab/route_map_spec.rb b/spec/lib/gitlab/route_map_spec.rb
new file mode 100644
index 00000000000..d56380bec50
--- /dev/null
+++ b/spec/lib/gitlab/route_map_spec.rb
@@ -0,0 +1,89 @@
+require 'spec_helper'
+
+describe Gitlab::RouteMap, lib: true do
+  describe '#initialize' do
+    context 'when the data is not YAML' do
+      it 'raises an error' do
+        expect { described_class.new('"') }.
+          to raise_error(Gitlab::RouteMap::FormatError, /valid YAML/)
+      end
+    end
+
+    context 'when the data is not a YAML array' do
+      it 'raises an error' do
+        expect { described_class.new(YAML.dump('foo')) }.
+          to raise_error(Gitlab::RouteMap::FormatError, /an array/)
+      end
+    end
+
+    context 'when an entry is not a hash' do
+      it 'raises an error' do
+        expect { described_class.new(YAML.dump(['foo'])) }.
+          to raise_error(Gitlab::RouteMap::FormatError, /a hash/)
+      end
+    end
+
+    context 'when an entry does not have a source key' do
+      it 'raises an error' do
+        expect { described_class.new(YAML.dump([{ 'public' => 'index.html' }])) }.
+          to raise_error(Gitlab::RouteMap::FormatError, /source key/)
+      end
+    end
+
+    context 'when an entry does not have a public key' do
+      it 'raises an error' do
+        expect { described_class.new(YAML.dump([{ 'source' => '/index\.html/' }])) }.
+          to raise_error(Gitlab::RouteMap::FormatError, /public key/)
+      end
+    end
+
+    context 'when an entry source does not start and end with a slash' do
+      it 'raises an error' do
+        expect { described_class.new(YAML.dump([{ 'source' => 'index.html', 'public' => 'index.html' }])) }.
+          to raise_error(Gitlab::RouteMap::FormatError, /a slash/)
+      end
+    end
+
+    context 'when an entry source is not a valid regex' do
+      it 'raises an error' do
+        expect { described_class.new(YAML.dump([{ 'source' => '/[/', 'public' => 'index.html' }])) }.
+          to raise_error(Gitlab::RouteMap::FormatError, /regular expression/)
+      end
+    end
+
+    context 'when all is good' do
+      it 'returns a route map' do
+        route_map = described_class.new(YAML.dump([{ 'source' => '/index\.html/', 'public' => 'index.html' }]))
+        expect(route_map.map).to eq([{ source: /^index\.html$/, public: 'index.html' }])
+      end
+    end
+  end
+
+  describe '#public_path_for_source_path' do
+    subject do
+      described_class.new(<<-'MAP'.strip_heredoc)
+        # Blogposts
+        - source: /source/posts/([0-9]{4})-([0-9]{2})-([0-9]{2})-(.+?)\..*/ # source/posts/2017-01-30-around-the-world-in-6-releases.html.md.erb
+          public: '\1/\2/\3/\4/' # 2017/01/30/around-the-world-in-6-releases/
+
+        # HTML files
+        - source: /source/(.+?\.html).*/ # source/index.html.haml
+          public: '\1' # index.html
+
+        # Other files
+        - source: /source/(.*)/ # source/images/blogimages/around-the-world-in-6-releases-cover.png
+          public: '\1' # images/blogimages/around-the-world-in-6-releases-cover.png
+      MAP
+    end
+
+    it 'returns the public path for a provided source path' do
+      expect(subject.public_path_for_source_path('source/posts/2017-01-30-around-the-world-in-6-releases.html.md.erb')).to eq('2017/01/30/around-the-world-in-6-releases/')
+
+      expect(subject.public_path_for_source_path('source/index.html.haml')).to eq('index.html')
+
+      expect(subject.public_path_for_source_path('source/images/blogimages/around-the-world-in-6-releases-cover.png')).to eq('images/blogimages/around-the-world-in-6-releases-cover.png')
+
+      expect(subject.public_path_for_source_path('.gitlab/route-map.yml')).to be_nil
+    end
+  end
+end
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index eba392044bf..e40a9bf4fbe 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -22,6 +22,25 @@ describe Environment, models: true do
   it { is_expected.to validate_length_of(:external_url).is_at_most(255) }
   it { is_expected.to validate_uniqueness_of(:external_url).scoped_to(:project_id) }
 
+  describe '.latest_for_commit' do
+    let!(:environment1) { create(:environment, project: project) }
+    let!(:environment2) { create(:environment, project: project) }
+    let!(:environment3) { create(:environment, project: project) }
+    let!(:deployment1) { create(:deployment, environment: environment1) }
+    let!(:deployment2) { create(:deployment, environment: environment2) }
+    let(:commit) { RepoHelpers.sample_commit }
+
+    before do
+      allow(environment1).to receive(:first_deployment_for).with(commit).and_return(deployment1)
+      allow(environment2).to receive(:first_deployment_for).with(commit).and_return(deployment2)
+      allow(environment3).to receive(:first_deployment_for).with(commit).and_return(nil)
+    end
+
+    it 'returns the environment that the commit was last deployed to' do
+      expect(Environment.latest_for_commit([environment1, environment2, environment3], commit)).to be(environment2)
+    end
+  end
+
   describe '#nullify_external_url' do
     it 'replaces a blank url with nil' do
       env = build(:environment, external_url: "")
@@ -301,4 +320,33 @@ describe Environment, models: true do
       end
     end
   end
+
+  describe '#external_url_for' do
+    let(:source_path) { 'source/file.html' }
+    let(:sha) { RepoHelpers.sample_commit.id }
+
+    before do
+      environment.external_url = 'http://example.com'
+    end
+
+    context 'when the public path is not known' do
+      before do
+        allow(project).to receive(:public_path_for_source_path).with(source_path, sha).and_return(nil)
+      end
+
+      it 'returns nil' do
+        expect(environment.external_url_for(source_path, sha)).to be_nil
+      end
+    end
+
+    context 'when the public path is known' do
+      before do
+        allow(project).to receive(:public_path_for_source_path).with(source_path, sha).and_return('file.html')
+      end
+
+      it 'returns the full external URL' do
+        expect(environment.external_url_for(source_path, sha)).to eq('http://example.com/file.html')
+      end
+    end
+  end
 end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 32ed1e96749..91a8f2d77ab 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -1069,6 +1069,30 @@ describe MergeRequest, models: true do
     end
   end
 
+  describe '#latest_environment' do
+    let(:project) { subject.project }
+    let!(:environment1) { create(:environment, project: project) }
+    let!(:environment2) { create(:environment, project: project) }
+    let!(:environment3) { create(:environment, project: project) }
+    let!(:deployment1) { create(:deployment, environment: environment1, ref: 'master', sha: commit.id) }
+    let!(:deployment2) { create(:deployment, environment: environment2, ref: 'feature', sha: commit.id) }
+    let(:commit) { subject.diff_head_commit }
+
+    before do
+      allow(environment1).to receive(:first_deployment_for).with(commit).and_return(deployment1)
+      allow(environment2).to receive(:first_deployment_for).with(commit).and_return(deployment2)
+      allow(environment3).to receive(:first_deployment_for).with(commit).and_return(nil)
+    end
+
+    before do
+      allow(subject).to receive(:environments).and_return([environment1, environment2, environment3])
+    end
+
+    it 'returns the environment that the commit was last deployed to' do
+      expect(subject.latest_environment).to eq(environment2)
+    end
+  end
+
   describe "#reload_diff" do
     let(:note) { create(:diff_note_on_merge_request, project: subject.project, noteable: subject) }
 
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index d7e6da02261..1072b324b22 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -1766,6 +1766,53 @@ describe Project, models: true do
           .to contain_exactly(environment)
       end
     end
+
+    context 'commit deployment' do
+      before do
+        create(:deployment, environment: environment, ref: 'master', sha: project.commit.id)
+      end
+
+      it 'returns environment' do
+        expect(project.environments_for(nil, commit: project.commit))
+          .to contain_exactly(environment)
+      end
+    end
+  end
+
+  describe '#latest_environment_for' do
+    let(:project) { create(:project) }
+    let!(:environment1) { create(:environment, project: project) }
+    let!(:environment2) { create(:environment, project: project) }
+    let!(:environment3) { create(:environment, project: project) }
+    let!(:deployment1) { create(:deployment, environment: environment1, ref: 'master', sha: commit.id) }
+    let!(:deployment2) { create(:deployment, environment: environment2, ref: 'feature', sha: commit.id) }
+    let(:commit) { project.commit }
+
+    before do
+      allow(environment1).to receive(:first_deployment_for).with(commit).and_return(deployment1)
+      allow(environment2).to receive(:first_deployment_for).with(commit).and_return(deployment2)
+      allow(environment3).to receive(:first_deployment_for).with(commit).and_return(nil)
+    end
+
+    context 'when specifying a ref' do
+      before do
+        allow(project).to receive(:environments_for).with('master', commit: commit).and_return([environment1])
+      end
+
+      it 'returns the environment that the commit was last deployed to from that ref' do
+        expect(project.latest_environment_for(commit, ref: 'master')).to eq(environment1)
+      end
+    end
+
+    context 'when not specifying a ref' do
+      before do
+        allow(project).to receive(:environments_for).with(nil, commit: commit).and_return([environment1, environment2])
+      end
+
+      it 'returns the environment that the commit was last deployed to' do
+        expect(project.latest_environment_for(commit)).to eq(environment2)
+      end
+    end
   end
 
   describe '#environments_recently_updated_on_branch' do
@@ -1858,6 +1905,82 @@ describe Project, models: true do
     it { expect(Project.inside_path(path)).to eq([project1]) }
   end
 
+  describe '#route_map_for' do
+    let(:project) { create(:project) }
+    let(:route_map) do
+      <<-MAP.strip_heredoc
+      - source: /source/(.*)/
+        public: '\\1'
+      MAP
+    end
+
+    before do
+      project.repository.commit_file(User.last, '.gitlab/route-map.yml', route_map, 'Add .gitlab/route-map.yml', 'master', false)
+    end
+
+    context 'when there is a .gitlab/route-map.yml at the commit' do
+      context 'when the route map is valid' do
+        it 'returns a route map' do
+          map = project.route_map_for(project.commit.sha)
+          expect(map).to be_a_kind_of(Gitlab::RouteMap)
+        end
+      end
+
+      context 'when the route map is invalid' do
+        let(:route_map) { 'INVALID' }
+
+        it 'returns nil' do
+          expect(project.route_map_for(project.commit.sha)).to be_nil
+        end
+      end
+    end
+
+    context 'when there is no .gitlab/route-map.yml at the commit' do
+      it 'returns nil' do
+        expect(project.route_map_for(project.commit.parent.sha)).to be_nil
+      end
+    end
+  end
+
+  describe '#public_path_for_source_path' do
+    let(:project) { create(:project) }
+    let(:route_map) do
+      Gitlab::RouteMap.new(<<-MAP.strip_heredoc)
+        - source: /source/(.*)/
+          public: '\\1'
+      MAP
+    end
+    let(:sha) { project.commit.id }
+
+    context 'when there is a route map' do
+      before do
+        allow(project).to receive(:route_map_for).with(sha).and_return(route_map)
+      end
+
+      context 'when the source path is mapped' do
+        it 'returns the public path' do
+          expect(project.public_path_for_source_path('source/file.html', sha)).to eq('file.html')
+        end
+      end
+
+      context 'when the source path is not mapped' do
+        it 'returns nil' do
+          expect(project.public_path_for_source_path('file.html', sha)).to be_nil
+        end
+      end
+    end
+
+    context 'when there is no route map' do
+      before do
+        allow(project).to receive(:route_map_for).with(sha).and_return(nil)
+      end
+
+      it 'returns nil' do
+        expect(project.public_path_for_source_path('source/file.html', sha)).to be_nil
+      end
+    end
+  end
+
   def enable_lfs
     allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
   end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 53b98ba05f8..7f97319aa2f 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -1782,4 +1782,40 @@ describe Repository, models: true do
       repository.refresh_method_caches(%i(readme license))
     end
   end
+
+  describe '#gitlab_ci_yml_for' do
+    before do
+      repository.commit_file(User.last, '.gitlab-ci.yml', 'CONTENT', 'Add .gitlab-ci.yml', 'master', false)
+    end
+
+    context 'when there is a .gitlab-ci.yml at the commit' do
+      it 'returns the content' do
+        expect(repository.gitlab_ci_yml_for(repository.commit.sha)).to eq('CONTENT')
+      end
+    end
+
+    context 'when there is no .gitlab-ci.yml at the commit' do
+      it 'returns nil' do
+        expect(repository.gitlab_ci_yml_for(repository.commit.parent.sha)).to be_nil
+      end
+    end
+  end
+
+  describe '#route_map_for' do
+    before do
+      repository.commit_file(User.last, '.gitlab/route-map.yml', 'CONTENT', 'Add .gitlab/route-map.yml', 'master', false)
+    end
+
+    context 'when there is a .gitlab/route-map.yml at the commit' do
+      it 'returns the content' do
+        expect(repository.route_map_for(repository.commit.sha)).to eq('CONTENT')
+      end
+    end
+
+    context 'when there is no .gitlab/route-map.yml at the commit' do
+      it 'returns nil' do
+        expect(repository.route_map_for(repository.commit.parent.sha)).to be_nil
+      end
+    end
+  end
 end
-- 
GitLab