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
|
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::Ml::CreateModelVersionService, feature_category: :mlops do
let_it_be(:model) { create(:ml_models) }
let_it_be(:project) { model.project }
let_it_be(:user) { project.owner }
let(:service) { described_class.new(model, params).execute }
let(:params) { { user: user } }
let(:audit_event) do
{
name: 'ml_model_version_created',
author: user,
scope: project
}
end
before do
allow(Gitlab::InternalEvents).to receive(:track_event)
allow(Gitlab::Audit::Auditor).to receive(:audit).and_call_original
end
subject(:model_version) { service.payload[:model_version] }
describe '#execute', :aggregate_failures do
let(:audit_context) do
audit_event.merge(target: model_version,
message: "MlModelVersion #{model_version.name}/#{model_version.version} created")
end
context 'when no versions exist and no value is passed for version' do
it 'creates a model version' do
expect { service }.to change { Ml::ModelVersion.count }.by(1)
.and change { Ml::Candidate.count }.by(1)
.and change { Packages::MlModel::Package.count }.by(1)
expect(model.reload.latest_version.version).to eq('1.0.0')
expect(service).to be_success
expect(model.latest_version.package.name).to eq(model.name)
expect(model.latest_version.package.version).to eq('1.0.0')
expect(Gitlab::InternalEvents).to have_received(:track_event).with(
'model_registry_ml_model_version_created',
{ project: model.project, user: user }
)
expect(Gitlab::Audit::Auditor).to have_received(:audit).with(audit_context)
end
end
context 'when a version exist and no value is passed for version' do
before do
create(:ml_model_versions, model: model, version: '3.0.0')
model.reload
end
it 'creates another model version and increments the version number' do
expect { service }.to change { Ml::ModelVersion.count }.by(1).and change { Ml::Candidate.count }.by(1)
expect(model.reload.latest_version.version).to eq('4.0.0')
expect(service).to be_success
expect(Gitlab::InternalEvents).to have_received(:track_event).with(
'model_registry_ml_model_version_created',
{ project: model.project, user: user }
)
expect(Gitlab::Audit::Auditor).to have_received(:audit).with(audit_context)
end
end
context 'when a version is created and the package already exists' do
it 'does not creates a package' do
next_version = Ml::IncrementVersionService.new(model.latest_version.try(:version)).execute
create(:ml_model_package, name: model.name, version: next_version, project: model.project)
expect { service }.to change { Ml::ModelVersion.count }.by(1).and not_change {
Packages::MlModel::Package.count
}
expect(model.reload.latest_version.package.name).to eq(model.name)
expect(model.latest_version.package.version).to eq(model.latest_version.version)
expect(service).to be_success
expect(Gitlab::Audit::Auditor).to have_received(:audit).with(audit_context)
end
end
context 'when creation of a model_version fails' do
it 'returns error' do
allow_next_instance_of(::Ml::ModelVersion) do |model_version|
allow(model_version).to receive(:save).and_return(false)
errors = ActiveModel::Errors.new(model_version).tap { |e| e.add(:id, 'some error') }
allow(model_version).to receive(:errors).and_return(errors)
end
expect { service }.to not_change { Ml::ModelVersion.count }.and not_change { Packages::MlModel::Package.count }
expect(service).to be_error
expect(service.message).to include('Id some error')
expect(Gitlab::Audit::Auditor).not_to have_received(:audit)
end
end
context 'when a version is created and an existing package supplied' do
it 'does not creates a package' do
next_version = Ml::IncrementVersionService.new(model.latest_version.try(:version)).execute
package = create(:ml_model_package, name: model.name, version: next_version, project: model.project)
service = described_class.new(model, { package: package })
expect { service.execute }.to change { Ml::ModelVersion.count }.by(1).and not_change {
Packages::MlModel::Package.count
}
expect(model.reload.latest_version.package.name).to eq(model.name)
expect(model.latest_version.package.version).to eq(model.latest_version.version)
expect(Gitlab::Audit::Auditor).to have_received(:audit).with(audit_context)
end
context 'when metadata are supplied, add them as metadata' do
let(:metadata) { [{ key: 'key1', value: 'value1' }, { key: 'key2', value: 'value2' }] }
let(:params) { { metadata: metadata } }
it 'creates metadata records' do
expect { service }.to change { Ml::ModelVersion.count }.by(1)
expect(model_version.metadata.count).to eq 2
end
end
# TODO: Ensure consisted error responses https://gitlab.com/gitlab-org/gitlab/-/issues/429731
context 'for metadata with duplicate keys, it does not create duplicate records' do
let(:metadata) { [{ key: 'key1', value: 'value1' }, { key: 'key1', value: 'value2' }] }
let(:params) { { metadata: metadata } }
it 'raises an error' do
expect(service).to be_error
expect(service.message).to include("Validation failed: Name 'key1' already taken")
end
end
# # TODO: Ensure consisted error responses https://gitlab.com/gitlab-org/gitlab/-/issues/429731
context 'for metadata with invalid keys, it does not create invalid records' do
let(:metadata) { [{ key: 'key1', value: 'value1' }, { key: '', value: 'value2' }] }
let(:params) { { metadata: metadata } }
it 'raises an error' do
expect(service).to be_error
expect(service.message).to include("Validation failed: Name can't be blank")
end
end
end
context 'when a version string is supplied during creation' do
let(:params) { { version: '1.2.3' } }
it 'creates a package' do
expect { service }.to change { Ml::ModelVersion.count }.by(1).and change {
Packages::MlModel::Package.count
}.by(1)
expect(model.reload.latest_version.version).to eq('1.2.3')
expect(model.latest_version.package.version).to eq('1.2.3')
end
end
context 'when version string supplied is invalid' do
let(:params) { { version: 'invalid-version' } }
it 'returns error' do
expect { service }.to not_change { Ml::ModelVersion.count }.and not_change { Packages::MlModel::Package.count }
expect(service).to be_error
expect(model_version).to be_nil
expect(service.message).to include('Version must be semantic version')
end
end
end
end
|