diff --git a/app/services/velorum/models_service.rb b/app/services/velorum/models_service.rb index 6dad34fa..6d314d60 100644 --- a/app/services/velorum/models_service.rb +++ b/app/services/velorum/models_service.rb @@ -2,6 +2,8 @@ module Velorum class ModelsService + include Code0::ZeroTrack::Loggable + def initialize(client: nil, config: Sagittarius::Configuration.config[:velorum]) @client = client @config = config @@ -11,6 +13,13 @@ def execute return [] unless config[:enabled] client.models.models + rescue GRPC::BadStatus => e + logger.warn( + message: 'Failed to fetch Velorum models', + grpc_code: e.respond_to?(:code) ? e.code : nil, + grpc_details: e.respond_to?(:details) ? e.details : e.message + ) + [] end private diff --git a/spec/requests/graphql/query/ai_query_spec.rb b/spec/requests/graphql/query/ai_query_spec.rb index 4d91c483..ef05b662 100644 --- a/spec/requests/graphql/query/ai_query_spec.rb +++ b/spec/requests/graphql/query/ai_query_spec.rb @@ -85,4 +85,20 @@ expect(Sagittarius::Velorum::Client).not_to have_received(:new) end end + + context 'when Velorum is unreachable' do + before do + allow(client).to receive(:models).and_raise( + GRPC::BadStatus.new_status_exception(GRPC::Core::StatusCodes::UNAVAILABLE, 'Connection refused') + ) + end + + it 'returns enabled state and an empty model list without GraphQL errors' do + post_graphql(query, current_user: current_user) + + expect(graphql_data_at(:ai, :enabled)).to be(true) + expect(graphql_data_at(:ai, :models)).to eq([]) + expect(graphql_errors).to be_nil + end + end end diff --git a/spec/services/velorum/models_service_spec.rb b/spec/services/velorum/models_service_spec.rb new file mode 100644 index 00000000..978adf9b --- /dev/null +++ b/spec/services/velorum/models_service_spec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Velorum::ModelsService do + subject(:models) { described_class.new(client: client, config: config).execute } + + let(:client) { instance_double(Sagittarius::Velorum::Client) } + let(:config) { { enabled: true } } + let(:models_response) do + Tucana::Velorum::ModelsResponse.new( + models: [ + Tucana::Velorum::Model.new(identifier: 'gpt-5', name: 'GPT-5') + ] + ) + end + + before do + allow(client).to receive(:models).and_return(models_response) + end + + it 'returns models from Velorum' do + expect(models).to eq(models_response.models) + end + + context 'when Velorum is disabled' do + let(:config) { { enabled: false } } + + it 'returns an empty list without calling Velorum' do + expect(models).to eq([]) + expect(client).not_to have_received(:models) + end + end + + context 'when Velorum is unreachable' do + before do + allow(client).to receive(:models).and_raise( + GRPC::BadStatus.new_status_exception(GRPC::Core::StatusCodes::UNAVAILABLE, 'Connection refused') + ) + end + + it 'returns an empty list' do + expect(models).to eq([]) + end + end +end