Files
gitlab-ce/spec/validators/json_schema_validator_spec.rb
2025-06-27 03:12:01 +00:00

263 lines
8.0 KiB
Ruby

# frozen_string_literal: true
require 'spec_helper'
RSpec.describe JsonSchemaValidator, feature_category: :shared do
describe '#validates_each' do
let(:build_report_result) { build(:ci_build_report_result, :with_junit_success) }
subject { validator.validate(build_report_result) }
context 'when filename is set' do
let(:validator) { described_class.new(attributes: [:data], filename: "build_report_result_data") }
context 'when data is valid' do
it 'returns no errors' do
subject
expect(build_report_result.errors).to be_empty
end
end
context 'when data is invalid' do
context 'when error message is not provided' do
it 'returns default set error message i.e `must be a valid json schema`' do
build_report_result.data = { invalid: 'data' }
validator.validate(build_report_result)
expect(build_report_result.errors.size).to eq(1)
expect(build_report_result.errors.full_messages).to eq(["Data must be a valid json schema"])
end
end
end
context 'when error message is provided' do
let(:validator) { described_class.new(attributes: [:data], filename: "build_report_result_data", message: "error in build-report-json") }
it 'returns the provided error message' do
build_report_result.data = { invalid: 'data' }
validator.validate(build_report_result)
expect(build_report_result.errors.size).to eq(1)
expect(build_report_result.errors.full_messages).to eq(["Data error in build-report-json"])
end
end
end
context 'when filename is not set' do
let(:validator) { described_class.new(attributes: [:data]) }
it 'raises an ArgumentError' do
expect { subject }.to raise_error(ArgumentError)
end
end
context 'when filename is invalid' do
let(:validator) { described_class.new(attributes: [:data], filename: "invalid$filename") }
it 'raises a FilenameError' do
expect { subject }.to raise_error(described_class::FilenameError)
end
end
describe 'hash_conversion option' do
context 'when hash_conversion is enabled' do
let(:validator) { described_class.new(attributes: [:data], filename: "build_report_result_data", hash_conversion: true) }
it 'returns no errors' do
subject
expect(build_report_result.errors).to be_empty
end
end
end
context 'when detail_errors is true' do
let(:validator) { described_class.new(attributes: [:data], detail_errors: true, filename: "build_report_result_data") }
context 'when data is valid' do
it 'returns no errors' do
subject
expect(build_report_result.errors).to be_empty
end
end
context 'when data is invalid' do
it 'returns json schema is invalid' do
build_report_result.data = { invalid: 'data' }
subject
expect(build_report_result.errors.size).to eq(1)
expect(build_report_result.errors.full_messages).to match_array(
["Data object property at `/invalid` is a disallowed additional property"]
)
end
end
end
context 'when validating config with oneOf JSON schema' do
let(:config) do
{
run: [
{
name: 'hello_steps',
step: 'gitlab.com/gitlab-org/ci-cd/runner-tools/echo-step',
inputs: {
echo: 'hello steps!'
}
}
]
}
end
let(:job) { Gitlab::Ci::Config::Entry::Job.new(config, name: :rspec) }
let(:errors) { ActiveModel::Errors.new(job) }
let(:validator) do
described_class.new(
attributes: [:run],
base_directory: 'app/validators/json_schemas',
filename: 'run_steps',
hash_conversion: true,
detail_errors: true
)
end
before do
job.compose!
allow(job).to receive(:errors).and_return(errors)
end
subject { validator.validate(job) }
context 'when the value is a valid array of hashes' do
before do
allow(job).to receive(:read_attribute_for_validation).and_return(config[:run])
end
it 'returns no errors' do
subject
expect(job.errors).to be_empty
end
end
context 'when a required property is missing' do
before do
config[:run] = [{ name: 'hello_steps' }]
allow(job).to receive(:read_attribute_for_validation).and_return(config[:run])
end
it 'returns an error message' do
subject
expect(job.errors).not_to be_empty
expect("#{job.errors.first.attribute} #{job.errors.first.type}").to eq("run object at `/0` is missing required properties: step")
end
end
context 'when oneOf validation fails' do
before do
config[:run] = [nil]
allow(job).to receive(:read_attribute_for_validation).and_return(config[:run])
end
it 'returns an error message' do
subject
expect(job.errors).not_to be_empty
expect("#{job.errors.first.attribute} #{job.errors.first.type}").to eq(
"run value at `/0` is not an object"
)
end
end
context 'when there is a general validation error' do
before do
config[:run] = 'not an array'
allow(job).to receive(:read_attribute_for_validation).and_return(config[:run])
end
it 'returns an error message' do
subject
expect(job.errors).not_to be_empty
expect("#{job.errors.first.attribute} #{job.errors.first.type}").to eq("run value at root is not an array")
end
end
context 'when a non-array value violates oneOf constraint' do
let(:schema) do
{
"type" => "object",
"properties" => {
"run" => {
"oneOf" => [
{ required: ["step"], title: "step" },
{ required: ["script"], title: "script" }
]
}
}
}
end
let(:validator) do
described_class.new(
attributes: [:run],
filename: 'test_schema',
detail_errors: true
)
end
before do
config[:run] = 'C'
allow(job).to receive(:read_attribute_for_validation).and_return({ run: config[:run] })
allow(JSONSchemer).to receive(:schema).and_return(JSONSchemer.schema(schema))
allow(File).to receive(:read).with(anything).and_return(schema.to_json)
end
it 'returns an error message for oneOf violation without data pointer' do
subject
expect(job.errors).not_to be_empty
expect("#{job.errors.first.attribute} #{job.errors.first.type}").to eq("run should use only one of: step, script")
end
end
end
context 'when size_limit is specified' do
let(:validator) { described_class.new(attributes: [:data], filename: "build_report_result_data", size_limit: 10.kilobytes) }
context 'when data exceeds size limit' do
it 'returns size error message' do
build_report_result.data = { large_field: 'x' * 15000 }
subject
expect(build_report_result.errors.size).to eq(1)
expect(build_report_result.errors.full_messages.first).to include("is too large")
expect(build_report_result.errors.full_messages.first).to include("10 KiB")
end
end
context 'when data is within size limit' do
it 'validates schema normally' do
subject
expect(build_report_result.errors).to be_empty
end
end
end
end
describe '#schema' do
let(:validator) { described_class.new(attributes: [:data], filename: "build_report_result_data") }
it 'is instance of JSONSchemer schema' do
expect(validator.schema).to be_a(JSONSchemer::Schema)
end
end
end