remote_mirror_spec.rb 7.73 KB
Newer Older
1 2
require 'rails_helper'

3
describe RemoteMirror do
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
  describe 'URL validation' do
    context 'with a valid URL' do
      it 'should be valid' do
        remote_mirror = build(:remote_mirror)
        expect(remote_mirror).to be_valid
      end
    end

    context 'with an invalid URL' do
      it 'should not be valid' do
        remote_mirror = build(:remote_mirror, url: 'ftp://invalid.invalid')
        expect(remote_mirror).not_to be_valid
        expect(remote_mirror.errors[:url].size).to eq(2)
      end
    end
  end

21 22
  describe 'encrypting credentials' do
    context 'when setting URL for a first time' do
Valery Sizov's avatar
Valery Sizov committed
23
      it 'stores the URL without credentials' do
James Lopez's avatar
James Lopez committed
24
        mirror = create_mirror(url: 'http://foo:bar@test.com')
25 26 27 28

        expect(mirror.read_attribute(:url)).to eq('http://test.com')
      end

Valery Sizov's avatar
Valery Sizov committed
29
      it 'stores the credentials on a separate field' do
James Lopez's avatar
James Lopez committed
30
        mirror = create_mirror(url: 'http://foo:bar@test.com')
31 32 33

        expect(mirror.credentials).to eq({ user: 'foo', password: 'bar' })
      end
34

Valery Sizov's avatar
Valery Sizov committed
35
      it 'handles credentials with large content' do
James Lopez's avatar
James Lopez committed
36
        mirror = create_mirror(url: 'http://bxnhm8dote33ct932r3xavslj81wxmr7o8yux8do10oozckkif:9ne7fuvjn40qjt35dgt8v86q9m9g9essryxj76sumg2ccl2fg26c0krtz2gzfpyq4hf22h328uhq6npuiq6h53tpagtsj7vsrz75@test.com')
37 38 39 40 41 42

        expect(mirror.credentials).to eq({
          user: 'bxnhm8dote33ct932r3xavslj81wxmr7o8yux8do10oozckkif',
          password: '9ne7fuvjn40qjt35dgt8v86q9m9g9essryxj76sumg2ccl2fg26c0krtz2gzfpyq4hf22h328uhq6npuiq6h53tpagtsj7vsrz75'
        })
      end
43 44 45
    end

    context 'when updating the URL' do
Valery Sizov's avatar
Valery Sizov committed
46
      it 'allows a new URL without credentials' do
James Lopez's avatar
James Lopez committed
47
        mirror = create_mirror(url: 'http://foo:bar@test.com')
48 49 50 51 52 53 54

        mirror.update_attribute(:url, 'http://test.com')

        expect(mirror.url).to eq('http://test.com')
        expect(mirror.credentials).to eq({ user: nil, password: nil })
      end

Valery Sizov's avatar
Valery Sizov committed
55
      it 'allows a new URL with credentials' do
James Lopez's avatar
James Lopez committed
56
        mirror = create_mirror(url: 'http://test.com')
57 58 59 60 61 62

        mirror.update_attribute(:url, 'http://foo:bar@test.com')

        expect(mirror.url).to eq('http://foo:bar@test.com')
        expect(mirror.credentials).to eq({ user: 'foo', password: 'bar' })
      end
63

Valery Sizov's avatar
Valery Sizov committed
64
      it 'updates the remote config if credentials changed' do
James Lopez's avatar
James Lopez committed
65
        mirror = create_mirror(url: 'http://foo:bar@test.com')
66 67 68 69
        repo = mirror.project.repository

        mirror.update_attribute(:url, 'http://foo:baz@test.com')

70 71
        config = repo.raw_repository.rugged.config
        expect(config["remote.#{mirror.ref_name}.url"]).to eq('http://foo:baz@test.com')
72
      end
73 74 75 76 77
    end
  end

  describe '#safe_url' do
    context 'when URL contains credentials' do
Valery Sizov's avatar
Valery Sizov committed
78
      it 'masks the credentials' do
James Lopez's avatar
James Lopez committed
79
        mirror = create_mirror(url: 'http://foo:bar@test.com')
80 81 82 83 84 85

        expect(mirror.safe_url).to eq('http://*****:*****@test.com')
      end
    end

    context 'when URL does not contain credentials' do
Valery Sizov's avatar
Valery Sizov committed
86
      it 'shows the full URL' do
James Lopez's avatar
James Lopez committed
87
        mirror = create_mirror(url: 'http://test.com')
88 89 90 91 92 93

        expect(mirror.safe_url).to eq('http://test.com')
      end
    end
  end

James Lopez's avatar
James Lopez committed
94 95 96 97 98 99 100
  context 'stuck mirrors' do
    it 'includes mirrors stuck in started with no last_update_at set' do
      mirror = create_mirror(url: 'http://cantbeblank',
                             update_status: 'started',
                             last_update_at: nil,
                             updated_at: 25.hours.ago)

101
      expect(described_class.stuck.last).to eq(mirror)
James Lopez's avatar
James Lopez committed
102 103 104
    end
  end

105
  context '#sync' do
106
    let(:remote_mirror) { create(:project, :repository, :remote_mirror).remote_mirrors.first }
107

108 109
    around do |example|
      Timecop.freeze { example.run }
110 111
    end

112 113 114 115 116 117 118 119 120 121 122 123
    context 'repository mirrors not licensed' do
      before do
        stub_licensed_features(repository_mirrors: false)
      end

      it 'does not schedule RepositoryUpdateRemoteMirrorWorker' do
        expect(RepositoryUpdateRemoteMirrorWorker).not_to receive(:perform_in)

        remote_mirror.sync
      end
    end

124 125 126 127 128 129 130 131
    context 'with remote mirroring disabled' do
      it 'returns nil' do
        remote_mirror.update_attributes(enabled: false)

        expect(remote_mirror.sync).to be_nil
      end
    end

132 133 134 135 136 137 138
    context 'as a Geo secondary' do
      it 'returns nil' do
        allow(Gitlab::Geo).to receive(:secondary?).and_return(true)

        expect(remote_mirror.sync).to be_nil
      end
    end
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184

    context 'with remote mirroring enabled' do
      context 'with only protected branches enabled' do
        context 'when it did not update in the last minute' do
          it 'schedules a RepositoryUpdateRemoteMirrorWorker to run now' do
            expect(RepositoryUpdateRemoteMirrorWorker).to receive(:perform_async).with(remote_mirror.id, Time.now)

            remote_mirror.sync
          end
        end

        context 'when it did update in the last minute' do
          it 'schedules a RepositoryUpdateRemoteMirrorWorker to run in the next minute' do
            remote_mirror.last_update_started_at = Time.now - 30.seconds

            expect(RepositoryUpdateRemoteMirrorWorker).to receive(:perform_in).with(RemoteMirror::PROTECTED_BACKOFF_DELAY, remote_mirror.id, Time.now)

            remote_mirror.sync
          end
        end
      end

      context 'with only protected branches disabled' do
        before do
          remote_mirror.only_protected_branches = false
        end

        context 'when it did not update in the last 5 minutes' do
          it 'schedules a RepositoryUpdateRemoteMirrorWorker to run now' do
            expect(RepositoryUpdateRemoteMirrorWorker).to receive(:perform_async).with(remote_mirror.id, Time.now)

            remote_mirror.sync
          end
        end

        context 'when it did update within the last 5 minutes' do
          it 'schedules a RepositoryUpdateRemoteMirrorWorker to run in the next 5 minutes' do
            remote_mirror.last_update_started_at = Time.now - 30.seconds

            expect(RepositoryUpdateRemoteMirrorWorker).to receive(:perform_in).with(RemoteMirror::UNPROTECTED_BACKOFF_DELAY, remote_mirror.id, Time.now)

            remote_mirror.sync
          end
        end
      end
    end
185 186
  end

187
  context '#updated_since?' do
188
    let(:remote_mirror) { create(:project, :repository, :remote_mirror).remote_mirrors.first }
189 190
    let(:timestamp) { Time.now - 5.minutes }

191 192
    around do |example|
      Timecop.freeze { example.run }
193 194
    end

195 196
    before do
      remote_mirror.update_attributes(last_update_started_at: Time.now)
197 198
    end

199 200 201 202 203 204
    context 'when remote mirror does not have status failed' do
      it 'returns true when last update started after the timestamp' do
        expect(remote_mirror.updated_since?(timestamp)).to be true
      end

      it 'returns false when last update started before the timestamp' do
Rémy Coutable's avatar
Rémy Coutable committed
205
        expect(remote_mirror.updated_since?(Time.now + 5.minutes)).to be false
206 207 208 209 210 211 212 213 214 215 216 217
      end
    end

    context 'when remote mirror has status failed' do
      it 'returns false when last update started after the timestamp' do
        remote_mirror.update_attributes(update_status: 'failed')

        expect(remote_mirror.updated_since?(timestamp)).to be false
      end
    end
  end

218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
  context 'no project' do
    it 'includes mirror with a project in pending_delete' do
      mirror = create_mirror(url: 'http://cantbeblank',
                             update_status: 'finished',
                             enabled: true,
                             last_update_at: nil,
                             updated_at: 25.hours.ago)
      project = mirror.project
      project.pending_delete = true
      project.save
      mirror.reload

      expect(mirror.sync).to be_nil
      expect(mirror.valid?).to be_truthy
      expect(mirror.update_status).to eq('finished')
    end
  end

James Lopez's avatar
James Lopez committed
236
  def create_mirror(params)
237
    project = FactoryBot.create(:project, :repository)
James Lopez's avatar
James Lopez committed
238
    project.remote_mirrors.create!(params)
239 240
  end
end