File: download_downstream_artifact_spec.rb

package info (click to toggle)
gitlab 17.6.5-19
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 629,368 kB
  • sloc: ruby: 1,915,304; javascript: 557,307; sql: 60,639; xml: 6,509; sh: 4,567; makefile: 1,239; python: 406
file content (180 lines) | stat: -rw-r--r-- 6,242 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
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
# frozen_string_literal: true

require 'fast_spec_helper'
require 'gitlab/rspec/all'
require_relative '../../scripts/download-downstream-artifact'

# rubocop:disable RSpec/VerifiedDoubles -- doubles are simple mocks of a few methods from external code

RSpec.describe DownloadDownstreamArtifact, feature_category: :tooling do
  include StubENV

  subject(:execute) { described_class.new(options).execute }

  before do
    stub_env('PROJECT_TOKEN_FOR_CI_SCRIPTS_API_USAGE', nil)
    stub_env('CI_PROJECT_ID', nil)
    stub_env('CI_PIPELINE_ID', nil)
    stub_env('CI_API_V4_URL', nil)
    stub_env('DOWNSTREAM_PROJECT', nil)
    stub_env('DOWNSTREAM_JOB_NAME', nil)
    stub_env('TRIGGER_JOB_NAME', nil)
    stub_env('DOWNSTREAM_JOB_ARTIFACT_PATH', nil)
    stub_env('OUTPUT_ARTIFACT_PATH', nil)
  end

  describe '#execute' do
    let(:options) do
      {
        api_token: 'asdf1234',
        endpoint: 'https://gitlab.com/api/v4',
        upstream_project: 'upstream/project',
        upstream_pipeline_id: 123,
        downstream_project: 'downstream/project',
        downstream_job_name: 'test-job',
        trigger_job_name: 'trigger-job',
        downstream_artifact_path: 'scores-DOWNSTREAM_JOB_ID.csv',
        output_artifact_path: 'scores.csv'
      }
    end

    let(:client) { double('Gitlab::Client') }
    let(:artifact_response) { double('io', read: 'artifact content') }

    let(:job) do
      Struct.new(:id, :name, :web_url).new(789, 'test-job', 'https://example.com/jobs/789')
    end

    let(:downstream_pipeline) do
      Struct.new(:id, :web_url).new(111, 'https://example.com/pipelines/111')
    end

    let(:pipeline_bridges) do
      double('pipeline_bridges', auto_paginate: [double(name: 'trigger-job', downstream_pipeline: downstream_pipeline)])
    end

    let(:expected_output) do
      <<~OUTPUT
        Fetching scores artifact from downstream pipeline triggered via trigger-job...
        Downstream pipeline is https://example.com/pipelines/111.
        Downstream job "test-job": https://example.com/jobs/789.
        Fetching artifact "scores-789.csv" from test-job...
        Artifact saved as scores.csv ...
      OUTPUT
    end

    before do
      allow(Gitlab).to receive(:client)
        .with(endpoint: options[:endpoint], private_token: options[:api_token])
        .and_return(client)

      allow(client).to receive(:pipeline_bridges).and_return(pipeline_bridges)
      allow(client).to receive(:pipeline).and_return(downstream_pipeline)
      allow(client).to receive(:pipeline_jobs).and_return([job])
      allow(client).to receive(:download_job_artifact_file).and_return(artifact_response)
      allow(File).to receive(:write)
    end

    it 'downloads artifact from downstream pipeline' do
      expect(client).to receive(:download_job_artifact_file).with('downstream/project', 789, 'scores-789.csv')

      expect { execute }.to output(expected_output).to_stdout
    end

    it 'saves artifact to output path' do
      expect(File).to receive(:write).with('scores.csv', 'artifact content')

      expect { execute }.to output(expected_output).to_stdout
    end

    context 'when options come from environment variables' do
      before do
        stub_env('PROJECT_TOKEN_FOR_CI_SCRIPTS_API_USAGE', 'asdf1234')
        stub_env('CI_PROJECT_ID', 'upstream/project')
        stub_env('CI_PIPELINE_ID', '123')
        stub_env('CI_API_V4_URL', 'https://gitlab.com/api/v4')
        stub_env('DOWNSTREAM_PROJECT', 'downstream/project')
        stub_env('DOWNSTREAM_JOB_NAME', 'test-job')
        stub_env('TRIGGER_JOB_NAME', 'trigger-job')
        stub_env('DOWNSTREAM_JOB_ARTIFACT_PATH', 'scores-DOWNSTREAM_JOB_ID.csv')
        stub_env('OUTPUT_ARTIFACT_PATH', 'scores.csv')

        stub_const('API::DEFAULT_OPTIONS', {
          project: ENV['CI_PROJECT_ID'],
          pipeline_id: ENV['CI_PIPELINE_ID'],
          api_token: ENV['PROJECT_TOKEN_FOR_CI_SCRIPTS_API_USAGE'],
          endpoint: ENV['CI_API_V4_URL']
        })
      end

      it 'uses the environment variable values' do
        options = described_class.options_from_env

        expect(File).to receive(:write)
        expect { described_class.new(options).execute }.to output(expected_output).to_stdout
      end
    end

    context 'when the downstream pipeline cannot be found' do
      let(:pipeline_bridges) do
        double('pipeline_bridges', auto_paginate: [double(name: 'trigger-job', downstream_pipeline: nil)])
      end

      it 'aborts' do
        expect(File).not_to receive(:write)
        expect { described_class.new(options).execute }
          .to output(
            %r{Could not find downstream pipeline triggered via trigger-job in project downstream/project}
          ).to_stderr
          .and raise_error(SystemExit)
      end
    end

    context 'when the downstream job cannot be found' do
      let(:job) { double('job', name: 'foo') }

      it 'aborts' do
        expect(File).not_to receive(:write)
        expect { described_class.new(options).execute }
          .to output(
            %r{Could not find job with name 'test-job' in https://example.com/pipelines/111}
          ).to_stderr
          .and raise_error(SystemExit)
      end
    end

    context 'when the downstream artifact cannot be found' do
      let(:artifact_response) { 'error' }

      it 'aborts' do
        expect(File).not_to receive(:write)
        expect { described_class.new(options).execute }
          .to output(
            %r{Could not download artifact. Request returned: error}
          ).to_stderr
          .and raise_error(SystemExit)
      end
    end
  end

  context 'when called without an API token' do
    let(:options) do
      {
        endpoint: 'https://gitlab.com/api/v4',
        upstream_project: 'upstream/project',
        upstream_pipeline_id: 123,
        downstream_project: 'downstream/project',
        downstream_job_name: 'test-job',
        trigger_job_name: 'trigger-job',
        downstream_artifact_path: 'scores-DOWNSTREAM_JOB_ID.csv',
        output_artifact_path: 'scores.csv'
      }
    end

    it 'raises an error' do
      expect { described_class.new(options) }.to raise_error(ArgumentError)
    end
  end
end

# rubocop:enable RSpec/VerifiedDoubles