# frozen_string_literal: true require 'spec_helper' RSpec.describe Ml::Experiment, feature_category: :mlops do let_it_be(:exp) { create(:ml_experiments) } let_it_be(:exp2) { create(:ml_experiments, project: exp.project) } let(:iid) { exp.iid } let(:exp_name) { exp.name } describe 'associations' do it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:user) } it { is_expected.to have_many(:candidates) } it { is_expected.to have_many(:metadata) } it { is_expected.to belong_to(:model).class_name('Ml::Model') } end describe '#destroy' do it 'allow experiment without model to be destroyed' do experiment = create(:ml_experiments, project: exp.project) expect { experiment.destroy! }.to change { Ml::Experiment.count }.by(-1) end it 'throws error when destroying experiment with model' do experiment = create(:ml_models, project: exp.project).default_experiment expect { experiment.destroy! }.to raise_error(ActiveRecord::ActiveRecordError) end end describe '.package_name' do describe '.package_name' do it { expect(exp.package_name).to eq("ml_experiment_#{exp.iid}") } end end describe '#by_project_id_and_iid' do subject { described_class.by_project_id_and_iid(exp.project_id, iid) } context 'if exists' do it { is_expected.to eq(exp) } end context 'if does not exist' do let(:iid) { non_existing_record_id } it { is_expected.to be(nil) } end end describe '#by_project_id_and_name' do subject { described_class.by_project_id_and_name(exp.project_id, exp_name) } context 'if exists' do it { is_expected.to eq(exp) } end context 'if does not exist' do let(:exp_name) { 'hello' } it { is_expected.to be_nil } end end describe '#by_project_id' do let(:project_id) { exp.project_id } subject { described_class.by_project_id(project_id) } it { is_expected.to match_array([exp, exp2]) } context 'when project does not have experiment' do let(:project_id) { non_existing_record_iid } it { is_expected.to be_empty } end end describe '.find_or_create' do let(:name) { exp.name } let(:project) { exp.project } subject(:find_or_create) { described_class.find_or_create(project, name, exp.user) } context 'when experiments exists' do it 'fetches existing experiment', :aggregate_failures do expect { find_or_create }.not_to change { Ml::Experiment.count } expect(find_or_create).to eq(exp) end end context 'when experiments does not exist' do let(:name) { 'a new experiment' } it 'creates the experiment', :aggregate_failures do expect { find_or_create }.to change { Ml::Experiment.count }.by(1) expect(find_or_create.name).to eq(name) expect(find_or_create.user).to eq(exp.user) expect(find_or_create.project).to eq(project) end end context 'when experiment name exists but project is different' do let(:project) { create(:project) } it 'creates a model', :aggregate_failures do expect { find_or_create }.to change { Ml::Experiment.count }.by(1) expect(find_or_create.name).to eq(name) expect(find_or_create.user).to eq(exp.user) expect(find_or_create.project).to eq(project) end end end describe '#with_candidate_count' do let_it_be(:exp3) do create(:ml_experiments, project: exp.project).tap do |e| create_list(:ml_candidates, 3, experiment: e, user: nil) create(:ml_candidates, experiment: exp2, user: nil) end end subject { described_class.with_candidate_count.to_h { |e| [e.id, e.candidate_count] } } it 'fetches the candidate count', :aggregate_failures do expect(subject[exp.id]).to eq(0) expect(subject[exp2.id]).to eq(1) expect(subject[exp3.id]).to eq(3) end end describe '#package_for_experiment?' do using RSpec::Parameterized::TableSyntax subject { described_class.package_for_experiment?(package_name) } where(:package_name, :id) do 'ml_experiment_1234' | true 'ml_experiment_1234abc' | false 'ml_experiment_abc' | false 'ml_experiment_' | false 'blah' | false end with_them do it { is_expected.to be(id) } end end end