Skip to content
Snippets Groups Projects
Commit d708c712 authored by Fabio Pitino's avatar Fabio Pitino :two:
Browse files

Merge branch 'remove-ci-legacy-stage' into 'master'

Removes Ci::LegacyStage

See merge request !91060
parents e3b53089 8f4dfb32
No related branches found
No related tags found
1 merge request!91060Removes Ci::LegacyStage
Pipeline #583593119 failed
Pipeline: GitLab

#583610866

    Showing
    with 48 additions and 561 deletions
    ......@@ -5512,7 +5512,6 @@ Layout/LineLength:
    - 'spec/presenters/alert_management/alert_presenter_spec.rb'
    - 'spec/presenters/blob_presenter_spec.rb'
    - 'spec/presenters/blobs/notebook_presenter_spec.rb'
    - 'spec/presenters/ci/legacy_stage_presenter_spec.rb'
    - 'spec/presenters/ci/pipeline_artifacts/code_quality_mr_diff_presenter_spec.rb'
    - 'spec/presenters/ci/pipeline_presenter_spec.rb'
    - 'spec/presenters/clusters/cluster_presenter_spec.rb'
    ......
    ......@@ -554,7 +554,6 @@ Rails/SkipsModelValidations:
    - 'spec/models/ci/build_dependencies_spec.rb'
    - 'spec/models/ci/build_spec.rb'
    - 'spec/models/ci/group_spec.rb'
    - 'spec/models/ci/legacy_stage_spec.rb'
    - 'spec/models/ci/pipeline_schedule_spec.rb'
    - 'spec/models/ci/pipeline_spec.rb'
    - 'spec/models/ci/processable_spec.rb'
    ......
    ......@@ -2620,7 +2620,6 @@ RSpec/ContextWording:
    - 'spec/models/ci/deleted_object_spec.rb'
    - 'spec/models/ci/job_artifact_spec.rb'
    - 'spec/models/ci/job_token/project_scope_link_spec.rb'
    - 'spec/models/ci/legacy_stage_spec.rb'
    - 'spec/models/ci/namespace_mirror_spec.rb'
    - 'spec/models/ci/pending_build_spec.rb'
    - 'spec/models/ci/pipeline_artifact_spec.rb'
    ......
    ......@@ -153,7 +153,6 @@ Style/IfUnlessModifier:
    - 'app/models/ci/build.rb'
    - 'app/models/ci/build_trace_chunk.rb'
    - 'app/models/ci/job_artifact.rb'
    - 'app/models/ci/legacy_stage.rb'
    - 'app/models/ci/pipeline.rb'
    - 'app/models/ci/runner.rb'
    - 'app/models/ci/running_build.rb'
    ......
    ......@@ -79,7 +79,6 @@ Style/PercentLiteralDelimiters:
    - 'app/models/bulk_imports/file_transfer/project_config.rb'
    - 'app/models/ci/build.rb'
    - 'app/models/ci/build_runner_session.rb'
    - 'app/models/ci/legacy_stage.rb'
    - 'app/models/ci/pipeline.rb'
    - 'app/models/clusters/applications/cert_manager.rb'
    - 'app/models/clusters/platforms/kubernetes.rb'
    ......
    ......@@ -175,7 +175,7 @@ def status
    end
    def stage
    @stage = pipeline.legacy_stage(params[:stage])
    @stage = pipeline.stage(params[:stage])
    return not_found unless @stage
    render json: StageSerializer
    ......
    # frozen_string_literal: true
    module Ci
    # Currently this is artificial object, constructed dynamically
    # We should migrate this object to actual database record in the future
    class LegacyStage
    include StaticModel
    include Presentable
    attr_reader :pipeline, :name
    delegate :project, to: :pipeline
    def initialize(pipeline, name:, status: nil, warnings: nil)
    @pipeline = pipeline
    @name = name
    @status = status
    # support ints and booleans
    @has_warnings = ActiveRecord::Type::Boolean.new.cast(warnings)
    end
    def groups
    @groups ||= Ci::Group.fabricate(project, self)
    end
    def to_param
    name
    end
    def statuses_count
    @statuses_count ||= statuses.count
    end
    def status
    @status ||= statuses.latest.composite_status(project: project)
    end
    def detailed_status(current_user)
    Gitlab::Ci::Status::Stage::Factory
    .new(self, current_user)
    .fabricate!
    end
    def latest_statuses
    statuses.ordered.latest
    end
    def statuses
    @statuses ||= pipeline.statuses.where(stage: name)
    end
    def builds
    @builds ||= pipeline.builds.where(stage: name)
    end
    def success?
    status.to_s == 'success'
    end
    def has_warnings?
    # lazilly calculate the warnings
    if @has_warnings.nil?
    @has_warnings = statuses.latest.failed_but_allowed.any?
    end
    @has_warnings
    end
    def manual_playable?
    %[manual scheduled skipped].include?(status.to_s)
    end
    end
    end
    ......@@ -496,40 +496,16 @@ def stages_names
    .pluck(:stage, :stage_idx).map(&:first)
    end
    def legacy_stage(name)
    stage = Ci::LegacyStage.new(self, name: name)
    stage unless stage.statuses_count == 0
    end
    def ref_exists?
    project.repository.ref_exists?(git_ref)
    rescue Gitlab::Git::Repository::NoRepository
    false
    end
    def legacy_stages_using_composite_status
    stages = latest_statuses_ordered_by_stage.group_by(&:stage)
    stages.map do |stage_name, jobs|
    composite_status = Gitlab::Ci::Status::Composite
    .new(jobs)
    Ci::LegacyStage.new(self,
    name: stage_name,
    status: composite_status.status,
    warnings: composite_status.warnings?)
    end
    end
    def triggered_pipelines_with_preloads
    triggered_pipelines.preload(:source_job)
    end
    # TODO: Remove usage of this method in templates
    def legacy_stages
    legacy_stages_using_composite_status
    end
    def valid_commit_sha
    if self.sha == Gitlab::Git::BLANK_SHA
    self.errors.add(:sha, " cant be 00000000 (branch removal)")
    ......@@ -1232,6 +1208,10 @@ def source_ref_slug
    Gitlab::Utils.slugify(source_ref.to_s)
    end
    def stage(name)
    stages.find_by(name: name)
    end
    def find_stage_by_name!(name)
    stages.find_by!(name: name)
    end
    ......
    # frozen_string_literal: true
    module Ci
    class LegacyStagePresenter < Gitlab::View::Presenter::Delegated
    presents ::Ci::LegacyStage, as: :legacy_stage
    def latest_ordered_statuses
    preload_statuses(legacy_stage.statuses.latest_ordered)
    end
    def retried_ordered_statuses
    preload_statuses(legacy_stage.statuses.retried_ordered)
    end
    private
    def preload_statuses(statuses)
    Preloaders::CommitStatusPreloader.new(statuses).execute(Ci::StagePresenter::PRELOADED_RELATIONS)
    statuses
    end
    end
    end
    # frozen_string_literal: true
    FactoryBot.define do
    factory :ci_stage, class: 'Ci::LegacyStage' do
    skip_create
    transient do
    name { 'test' }
    status { nil }
    warnings { nil }
    pipeline factory: :ci_empty_pipeline
    end
    initialize_with do
    Ci::LegacyStage.new(pipeline, name: name,
    status: status,
    warnings: warnings)
    end
    end
    factory :ci_stage_entity, class: 'Ci::Stage' do
    project factory: :project
    pipeline factory: :ci_empty_pipeline
    ......
    ......@@ -8,7 +8,7 @@
    let(:pipeline) { create(:ci_empty_pipeline, project: project) }
    let(:stage) do
    build(:ci_stage, pipeline: pipeline, name: 'test')
    build(:ci_stage_entity, pipeline: pipeline, name: 'test')
    end
    subject do
    ......
    ......@@ -7,9 +7,7 @@
    let(:project) { create(:project) }
    let(:pipeline) { create(:ci_empty_pipeline, project: project) }
    let(:stage) do
    build(:ci_stage, pipeline: pipeline, name: 'test')
    end
    let(:stage) { create(:ci_stage_entity, pipeline: pipeline) }
    subject do
    described_class.new(stage, user)
    ......@@ -26,11 +24,7 @@
    context 'when stage has a core status' do
    (Ci::HasStatus::AVAILABLE_STATUSES - %w(manual skipped scheduled)).each do |core_status|
    context "when core status is #{core_status}" do
    before do
    create(:ci_build, pipeline: pipeline, stage: 'test', status: core_status)
    create(:commit_status, pipeline: pipeline, stage: 'test', status: core_status)
    create(:ci_build, pipeline: pipeline, stage: 'build', status: :failed)
    end
    let(:stage) { create(:ci_stage_entity, pipeline: pipeline, status: core_status) }
    it "fabricates a core status #{core_status}" do
    expect(status).to be_a(
    ......@@ -48,12 +42,12 @@
    context 'when stage has warnings' do
    let(:stage) do
    build(:ci_stage, name: 'test', status: :success, pipeline: pipeline)
    create(:ci_stage_entity, status: :success, pipeline: pipeline)
    end
    before do
    create(:ci_build, :allowed_to_fail, :failed,
    stage: 'test', pipeline: stage.pipeline)
    stage_id: stage.id, pipeline: stage.pipeline)
    end
    it 'fabricates extended "success with warnings" status' do
    ......@@ -70,11 +64,7 @@
    context 'when stage has manual builds' do
    (Ci::HasStatus::BLOCKED_STATUS + ['skipped']).each do |core_status|
    context "when status is #{core_status}" do
    before do
    create(:ci_build, pipeline: pipeline, stage: 'test', status: core_status)
    create(:commit_status, pipeline: pipeline, stage: 'test', status: core_status)
    create(:ci_build, pipeline: pipeline, stage: 'build', status: :manual)
    end
    let(:stage) { create(:ci_stage_entity, pipeline: pipeline, status: core_status) }
    it 'fabricates a play manual status' do
    expect(status).to be_a(Gitlab::Ci::Status::Stage::PlayManual)
    ......
    # frozen_string_literal: true
    require 'spec_helper'
    RSpec.describe Ci::LegacyStage do
    let(:stage) { build(:ci_stage) }
    let(:pipeline) { stage.pipeline }
    let(:stage_name) { stage.name }
    describe '#expectations' do
    subject { stage }
    it { is_expected.to include_module(StaticModel) }
    it { is_expected.to respond_to(:pipeline) }
    it { is_expected.to respond_to(:name) }
    it { is_expected.to delegate_method(:project).to(:pipeline) }
    end
    describe '#statuses' do
    let!(:stage_build) { create_job(:ci_build) }
    let!(:commit_status) { create_job(:commit_status) }
    let!(:other_build) { create_job(:ci_build, stage: 'other stage') }
    subject { stage.statuses }
    it "returns only matching statuses" do
    is_expected.to contain_exactly(stage_build, commit_status)
    end
    end
    describe '#groups' do
    before do
    create_job(:ci_build, name: 'rspec 0 2')
    create_job(:ci_build, name: 'rspec 0 1')
    create_job(:ci_build, name: 'spinach 0 1')
    create_job(:commit_status, name: 'aaaaa')
    end
    it 'returns an array of three groups' do
    expect(stage.groups).to be_a Array
    expect(stage.groups).to all(be_a Ci::Group)
    expect(stage.groups.size).to eq 3
    end
    it 'returns groups with correctly ordered statuses' do
    expect(stage.groups.first.jobs.map(&:name))
    .to eq ['aaaaa']
    expect(stage.groups.second.jobs.map(&:name))
    .to eq ['rspec 0 1', 'rspec 0 2']
    expect(stage.groups.third.jobs.map(&:name))
    .to eq ['spinach 0 1']
    end
    it 'returns groups with correct names' do
    expect(stage.groups.map(&:name))
    .to eq %w[aaaaa rspec spinach]
    end
    context 'when a name is nil on legacy pipelines' do
    before do
    pipeline.builds.first.update_attribute(:name, nil)
    end
    it 'returns an array of three groups' do
    expect(stage.groups.map(&:name))
    .to eq ['', 'aaaaa', 'rspec', 'spinach']
    end
    end
    end
    describe '#statuses_count' do
    before do
    create_job(:ci_build)
    create_job(:ci_build, stage: 'other stage')
    end
    subject { stage.statuses_count }
    it "counts statuses only from current stage" do
    is_expected.to eq(1)
    end
    end
    describe '#builds' do
    let!(:stage_build) { create_job(:ci_build) }
    let!(:commit_status) { create_job(:commit_status) }
    subject { stage.builds }
    it "returns only builds" do
    is_expected.to contain_exactly(stage_build)
    end
    end
    describe '#status' do
    subject { stage.status }
    context 'if status is already defined' do
    let(:stage) { build(:ci_stage, status: 'success') }
    it "returns defined status" do
    is_expected.to eq('success')
    end
    end
    context 'if status has to be calculated' do
    let!(:stage_build) { create_job(:ci_build, status: :failed) }
    it "returns status of a build" do
    is_expected.to eq('failed')
    end
    context 'and builds are retried' do
    let!(:new_build) { create_job(:ci_build, status: :success) }
    before do
    stage_build.update!(retried: true)
    end
    it "returns status of latest build" do
    is_expected.to eq('success')
    end
    end
    end
    end
    describe '#detailed_status' do
    let(:user) { create(:user) }
    subject { stage.detailed_status(user) }
    context 'when build is created' do
    let!(:stage_build) { create_job(:ci_build, status: :created) }
    it 'returns detailed status for created stage' do
    expect(subject.text).to eq s_('CiStatusText|created')
    end
    end
    context 'when build is pending' do
    let!(:stage_build) { create_job(:ci_build, status: :pending) }
    it 'returns detailed status for pending stage' do
    expect(subject.text).to eq s_('CiStatusText|pending')
    end
    end
    context 'when build is running' do
    let!(:stage_build) { create_job(:ci_build, status: :running) }
    it 'returns detailed status for running stage' do
    expect(subject.text).to eq s_('CiStatus|running')
    end
    end
    context 'when build is successful' do
    let!(:stage_build) { create_job(:ci_build, status: :success) }
    it 'returns detailed status for successful stage' do
    expect(subject.text).to eq s_('CiStatusText|passed')
    end
    end
    context 'when build is failed' do
    let!(:stage_build) { create_job(:ci_build, status: :failed) }
    it 'returns detailed status for failed stage' do
    expect(subject.text).to eq s_('CiStatusText|failed')
    end
    end
    context 'when build is canceled' do
    let!(:stage_build) { create_job(:ci_build, status: :canceled) }
    it 'returns detailed status for canceled stage' do
    expect(subject.text).to eq s_('CiStatusText|canceled')
    end
    end
    context 'when build is skipped' do
    let!(:stage_build) { create_job(:ci_build, status: :skipped) }
    it 'returns detailed status for skipped stage' do
    expect(subject.text).to eq s_('CiStatusText|skipped')
    end
    end
    end
    describe '#success?' do
    context 'when stage is successful' do
    before do
    create_job(:ci_build, status: :success)
    create_job(:generic_commit_status, status: :success)
    end
    it 'is successful' do
    expect(stage).to be_success
    end
    end
    context 'when stage is not successful' do
    before do
    create_job(:ci_build, status: :failed)
    create_job(:generic_commit_status, status: :success)
    end
    it 'is not successful' do
    expect(stage).not_to be_success
    end
    end
    end
    describe '#has_warnings?' do
    context 'when stage has warnings' do
    context 'when using memoized warnings flag' do
    context 'when there are warnings' do
    let(:stage) { build(:ci_stage, warnings: true) }
    it 'returns true using memoized value' do
    expect(stage).not_to receive(:statuses)
    expect(stage).to have_warnings
    end
    end
    context 'when there are no warnings' do
    let(:stage) { build(:ci_stage, warnings: false) }
    it 'returns false using memoized value' do
    expect(stage).not_to receive(:statuses)
    expect(stage).not_to have_warnings
    end
    end
    end
    context 'when calculating warnings from statuses' do
    before do
    create(:ci_build, :failed, :allowed_to_fail,
    stage: stage_name, pipeline: pipeline)
    end
    it 'has warnings calculated from statuses' do
    expect(stage).to receive(:statuses).and_call_original
    expect(stage).to have_warnings
    end
    end
    end
    context 'when stage does not have warnings' do
    before do
    create(:ci_build, :success, stage: stage_name,
    pipeline: pipeline)
    end
    it 'does not have warnings calculated from statuses' do
    expect(stage).to receive(:statuses).and_call_original
    expect(stage).not_to have_warnings
    end
    end
    end
    def create_job(type, status: 'success', stage: stage_name, **opts)
    create(type, pipeline: pipeline, stage: stage, status: status, **opts)
    end
    it_behaves_like 'manual playable stage', :ci_stage
    end
    ......@@ -1333,48 +1333,6 @@ def create_build(name, status)
    status: 'success')
    end
    describe '#legacy_stages' do
    using RSpec::Parameterized::TableSyntax
    subject { pipeline.legacy_stages }
    context 'stages list' do
    it 'returns ordered list of stages' do
    expect(subject.map(&:name)).to eq(%w[build test deploy])
    end
    end
    context 'stages with statuses' do
    let(:statuses) do
    subject.map { |stage| [stage.name, stage.status] }
    end
    it 'returns list of stages with correct statuses' do
    expect(statuses).to eq([%w(build failed),
    %w(test success),
    %w(deploy running)])
    end
    end
    context 'when there is a stage with warnings' do
    before do
    create(:commit_status, pipeline: pipeline,
    stage: 'deploy',
    name: 'prod:2',
    stage_idx: 2,
    status: 'failed',
    allow_failure: true)
    end
    it 'populates stage with correct number of warnings' do
    deploy_stage = pipeline.legacy_stages.third
    expect(deploy_stage).not_to receive(:statuses)
    expect(deploy_stage).to have_warnings
    end
    end
    end
    describe '#stages_count' do
    it 'returns a valid number of stages' do
    expect(pipeline.stages_count).to eq(3)
    ......@@ -1388,32 +1346,6 @@ def create_build(name, status)
    end
    end
    describe '#legacy_stage' do
    subject { pipeline.legacy_stage('test') }
    let(:pipeline) { build(:ci_empty_pipeline, :created) }
    context 'with status in stage' do
    before do
    create(:commit_status, pipeline: pipeline, stage: 'test')
    end
    it { expect(subject).to be_a Ci::LegacyStage }
    it { expect(subject.name).to eq 'test' }
    it { expect(subject.statuses).not_to be_empty }
    end
    context 'without status in stage' do
    before do
    create(:commit_status, pipeline: pipeline, stage: 'build')
    end
    it 'return stage object' do
    is_expected.to be_nil
    end
    end
    end
    describe '#stages' do
    let(:pipeline) { build(:ci_empty_pipeline, :created) }
    ......@@ -4320,7 +4252,7 @@ def create_pipeline(status, ref, sha)
    end
    end
    describe '#find_stage_by_name' do
    describe 'fetching a stage by name' do
    let_it_be(:pipeline) { create(:ci_pipeline) }
    let(:stage_name) { 'test' }
    ......@@ -4336,19 +4268,37 @@ def create_pipeline(status, ref, sha)
    create_list(:ci_build, 2, pipeline: pipeline, stage: stage.name)
    end
    subject { pipeline.find_stage_by_name!(stage_name) }
    describe '#stage' do
    subject { pipeline.stage(stage_name) }
    context 'when stage exists' do
    it { is_expected.to eq(stage) }
    context 'when stage exists' do
    it { is_expected.to eq(stage) }
    end
    context 'when stage does not exist' do
    let(:stage_name) { 'build' }
    it 'returns nil' do
    is_expected.to be_nil
    end
    end
    end
    context 'when stage does not exist' do
    let(:stage_name) { 'build' }
    describe '#find_stage_by_name' do
    subject { pipeline.find_stage_by_name!(stage_name) }
    it 'raises an ActiveRecord exception' do
    expect do
    subject
    end.to raise_exception(ActiveRecord::RecordNotFound)
    context 'when stage exists' do
    it { is_expected.to eq(stage) }
    end
    context 'when stage does not exist' do
    let(:stage_name) { 'build' }
    it 'raises an ActiveRecord exception' do
    expect do
    subject
    end.to raise_exception(ActiveRecord::RecordNotFound)
    end
    end
    end
    end
    ......
    # frozen_string_literal: true
    require 'spec_helper'
    RSpec.describe Ci::LegacyStagePresenter do
    let(:legacy_stage) { create(:ci_stage) }
    let(:presenter) { described_class.new(legacy_stage) }
    let!(:build) { create(:ci_build, :tags, :artifacts, pipeline: legacy_stage.pipeline, stage: legacy_stage.name) }
    let!(:retried_build) { create(:ci_build, :tags, :artifacts, :retried, pipeline: legacy_stage.pipeline, stage: legacy_stage.name) }
    before do
    create(:generic_commit_status, pipeline: legacy_stage.pipeline, stage: legacy_stage.name)
    end
    describe '#latest_ordered_statuses' do
    subject(:latest_ordered_statuses) { presenter.latest_ordered_statuses }
    it 'preloads build tags' do
    expect(latest_ordered_statuses.second.association(:tags)).to be_loaded
    end
    it 'preloads build artifacts archive' do
    expect(latest_ordered_statuses.second.association(:job_artifacts_archive)).to be_loaded
    end
    it 'preloads build artifacts metadata' do
    expect(latest_ordered_statuses.second.association(:metadata)).to be_loaded
    end
    end
    describe '#retried_ordered_statuses' do
    subject(:retried_ordered_statuses) { presenter.retried_ordered_statuses }
    it 'preloads build tags' do
    expect(retried_ordered_statuses.first.association(:tags)).to be_loaded
    end
    it 'preloads build artifacts archive' do
    expect(retried_ordered_statuses.first.association(:job_artifacts_archive)).to be_loaded
    end
    it 'preloads build artifacts metadata' do
    expect(retried_ordered_statuses.first.association(:metadata)).to be_loaded
    end
    end
    end
    ......@@ -3,7 +3,7 @@
    require 'spec_helper'
    RSpec.describe Ci::StagePresenter do
    let(:stage) { create(:ci_stage) }
    let(:stage) { create(:ci_stage_entity) }
    let(:presenter) { described_class.new(stage) }
    let!(:build) { create(:ci_build, :tags, :artifacts, pipeline: stage.pipeline, stage: stage.name) }
    ......
    ......@@ -5,7 +5,7 @@
    RSpec.describe Ci::DagJobGroupEntity do
    let_it_be(:request) { double(:request) }
    let_it_be(:pipeline) { create(:ci_pipeline) }
    let_it_be(:stage) { create(:ci_stage, pipeline: pipeline) }
    let_it_be(:stage) { create(:ci_stage_entity, pipeline: pipeline) }
    let(:group) { Ci::Group.new(pipeline.project, stage, name: 'test', jobs: jobs) }
    let(:entity) { described_class.new(group, request: request) }
    ......@@ -14,7 +14,7 @@
    subject { entity.as_json }
    context 'when group contains 1 job' do
    let(:job) { create(:ci_build, stage: stage, pipeline: pipeline, name: 'test') }
    let(:job) { create(:ci_build, stage_id: stage.id, pipeline: pipeline, name: 'test') }
    let(:jobs) { [job] }
    it 'exposes a name' do
    ......@@ -38,8 +38,8 @@
    end
    context 'when group contains multiple parallel jobs' do
    let(:job_1) { create(:ci_build, stage: stage, pipeline: pipeline, name: 'test 1/2') }
    let(:job_2) { create(:ci_build, stage: stage, pipeline: pipeline, name: 'test 2/2') }
    let(:job_1) { create(:ci_build, stage_id: stage.id, pipeline: pipeline, name: 'test 1/2') }
    let(:job_2) { create(:ci_build, stage_id: stage.id, pipeline: pipeline, name: 'test 2/2') }
    let(:jobs) { [job_1, job_2] }
    it 'exposes a name' do
    ......
    ......@@ -6,10 +6,10 @@
    let_it_be(:pipeline) { create(:ci_pipeline) }
    let_it_be(:request) { double(:request) }
    let(:stage) { build(:ci_stage, pipeline: pipeline, name: 'test') }
    let(:stage) { create(:ci_stage_entity, pipeline: pipeline, name: 'test') }
    let(:entity) { described_class.new(stage, request: request) }
    let!(:job) { create(:ci_build, :success, pipeline: pipeline) }
    let!(:job) { create(:ci_build, :success, pipeline: pipeline, stage_id: stage.id) }
    describe '#as_json' do
    subject { entity.as_json }
    ......
    ......@@ -12,12 +12,12 @@
    end
    let(:stage) do
    build(:ci_stage, pipeline: pipeline, name: 'test')
    create(:ci_stage_entity, pipeline: pipeline, status: :success)
    end
    before do
    allow(request).to receive(:current_user).and_return(user)
    create(:ci_build, :success, pipeline: pipeline)
    create(:ci_build, :success, pipeline: pipeline, stage_id: stage.id)
    end
    describe '#as_json' do
    ......
    0% Loading or .
    You are about to add 0 people to the discussion. Proceed with caution.
    Finish editing this message first!
    Please register or to comment