467 lines
14 KiB
Ruby
467 lines
14 KiB
Ruby
# typed: false
|
|
require "rails_helper"
|
|
|
|
RSpec.describe TelegramBotLog, type: :model do
|
|
describe "validations" do
|
|
subject { build(:telegram_bot_log) }
|
|
|
|
it { should validate_presence_of(:telegram_user_id) }
|
|
it { should validate_presence_of(:telegram_chat_id) }
|
|
it { should validate_presence_of(:request_timestamp) }
|
|
it { should validate_presence_of(:status) }
|
|
it { should validate_presence_of(:search_results_count) }
|
|
|
|
it do
|
|
should validate_numericality_of(
|
|
:search_results_count,
|
|
).is_greater_than_or_equal_to(0)
|
|
end
|
|
|
|
it do
|
|
should validate_numericality_of(
|
|
:fingerprint_computation_time,
|
|
).is_greater_than(0).allow_nil
|
|
end
|
|
it do
|
|
should validate_numericality_of(:search_computation_time).is_greater_than(
|
|
0,
|
|
).allow_nil
|
|
end
|
|
|
|
it do
|
|
should validate_length_of(:processed_image_sha256).is_equal_to(
|
|
32,
|
|
).allow_nil
|
|
end
|
|
|
|
it do
|
|
should define_enum_for(:status)
|
|
.with_values(
|
|
success: "success",
|
|
error: "error",
|
|
no_results: "no_results",
|
|
invalid_image: "invalid_image",
|
|
)
|
|
.backed_by_column_of_type(:string)
|
|
.with_prefix(:status)
|
|
end
|
|
|
|
context "when fingerprint_computation_time is present" do
|
|
it "is valid with a positive value" do
|
|
subject.fingerprint_computation_time = 0.5
|
|
expect(subject).to be_valid
|
|
end
|
|
|
|
it "is invalid with zero" do
|
|
subject.fingerprint_computation_time = 0
|
|
expect(subject).not_to be_valid
|
|
expect(subject.errors[:fingerprint_computation_time]).to include(
|
|
"must be greater than 0",
|
|
)
|
|
end
|
|
|
|
it "is invalid with negative value" do
|
|
subject.fingerprint_computation_time = -0.1
|
|
expect(subject).not_to be_valid
|
|
expect(subject.errors[:fingerprint_computation_time]).to include(
|
|
"must be greater than 0",
|
|
)
|
|
end
|
|
end
|
|
|
|
context "when search_computation_time is present" do
|
|
it "is valid with a positive value" do
|
|
subject.search_computation_time = 0.3
|
|
expect(subject).to be_valid
|
|
end
|
|
|
|
it "is invalid with zero" do
|
|
subject.search_computation_time = 0
|
|
expect(subject).not_to be_valid
|
|
expect(subject.errors[:search_computation_time]).to include(
|
|
"must be greater than 0",
|
|
)
|
|
end
|
|
|
|
it "is invalid with negative value" do
|
|
subject.search_computation_time = -0.1
|
|
expect(subject).not_to be_valid
|
|
expect(subject.errors[:search_computation_time]).to include(
|
|
"must be greater than 0",
|
|
)
|
|
end
|
|
end
|
|
|
|
context "when download_time is present" do
|
|
it "is valid with a positive value" do
|
|
subject.download_time = 0.2
|
|
expect(subject).to be_valid
|
|
end
|
|
|
|
it "is invalid with zero" do
|
|
subject.download_time = 0
|
|
expect(subject).not_to be_valid
|
|
expect(subject.errors[:download_time]).to include(
|
|
"must be greater than 0",
|
|
)
|
|
end
|
|
|
|
it "is invalid with negative value" do
|
|
subject.download_time = -0.1
|
|
expect(subject).not_to be_valid
|
|
expect(subject.errors[:download_time]).to include(
|
|
"must be greater than 0",
|
|
)
|
|
end
|
|
end
|
|
|
|
context "when image_processing_time is present" do
|
|
it "is valid with a positive value" do
|
|
subject.image_processing_time = 0.1
|
|
expect(subject).to be_valid
|
|
end
|
|
|
|
it "is invalid with zero" do
|
|
subject.image_processing_time = 0
|
|
expect(subject).not_to be_valid
|
|
expect(subject.errors[:image_processing_time]).to include(
|
|
"must be greater than 0",
|
|
)
|
|
end
|
|
|
|
it "is invalid with negative value" do
|
|
subject.image_processing_time = -0.05
|
|
expect(subject).not_to be_valid
|
|
expect(subject.errors[:image_processing_time]).to include(
|
|
"must be greater than 0",
|
|
)
|
|
end
|
|
end
|
|
|
|
context "when total_request_time is present" do
|
|
it "is valid with a positive value" do
|
|
subject.total_request_time = 1.5
|
|
expect(subject).to be_valid
|
|
end
|
|
|
|
it "is invalid with zero" do
|
|
subject.total_request_time = 0
|
|
expect(subject).not_to be_valid
|
|
expect(subject.errors[:total_request_time]).to include(
|
|
"must be greater than 0",
|
|
)
|
|
end
|
|
|
|
it "is invalid with negative value" do
|
|
subject.total_request_time = -0.1
|
|
expect(subject).not_to be_valid
|
|
expect(subject.errors[:total_request_time]).to include(
|
|
"must be greater than 0",
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "associations" do
|
|
it { should belong_to(:processed_image).class_name("BlobFile").optional }
|
|
|
|
context "with processed image" do
|
|
let(:blob_file) { create(:blob_file, :image_blob) }
|
|
let(:log) { build(:telegram_bot_log, processed_image: blob_file) }
|
|
|
|
it "associates with BlobFile correctly" do
|
|
expect(log.processed_image).to eq(blob_file)
|
|
expect(log.processed_image_sha256).to eq(blob_file.sha256)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "scopes" do
|
|
let!(:user1_log1) do
|
|
create(:telegram_bot_log, telegram_user_id: 111, status: :success)
|
|
end
|
|
let!(:user1_log2) do
|
|
create(:telegram_bot_log, telegram_user_id: 111, status: :error)
|
|
end
|
|
let!(:user2_log) do
|
|
create(:telegram_bot_log, telegram_user_id: 222, status: :success)
|
|
end
|
|
let!(:no_results_log) { create(:telegram_bot_log, :with_no_results) }
|
|
let!(:old_log) { create(:telegram_bot_log, request_timestamp: 1.week.ago) }
|
|
let!(:recent_log) do
|
|
create(:telegram_bot_log, request_timestamp: 1.hour.ago)
|
|
end
|
|
|
|
describe ".for_user" do
|
|
it "returns logs for specific user" do
|
|
expect(TelegramBotLog.for_user(111)).to contain_exactly(
|
|
user1_log1,
|
|
user1_log2,
|
|
)
|
|
expect(TelegramBotLog.for_user(222)).to contain_exactly(user2_log)
|
|
end
|
|
end
|
|
|
|
describe ".successful" do
|
|
it "returns only successful logs" do
|
|
successful_logs = TelegramBotLog.successful
|
|
expect(successful_logs).to include(user1_log1, user2_log, recent_log)
|
|
expect(successful_logs).not_to include(user1_log2, no_results_log)
|
|
end
|
|
end
|
|
|
|
describe ".with_results" do
|
|
it "returns logs with search results count > 0" do
|
|
with_results = TelegramBotLog.with_results
|
|
expect(with_results).not_to include(no_results_log)
|
|
expect(with_results).to include(user1_log1, user2_log)
|
|
end
|
|
end
|
|
|
|
describe ".recent" do
|
|
it "orders by request_timestamp desc" do
|
|
recent_logs = TelegramBotLog.recent
|
|
expect(recent_logs.first.request_timestamp).to be >
|
|
recent_logs.last.request_timestamp
|
|
end
|
|
end
|
|
|
|
describe ".by_date_range" do
|
|
it "returns logs within date range" do
|
|
start_date = 2.days.ago
|
|
end_date = Time.current
|
|
logs_in_range = TelegramBotLog.by_date_range(start_date, end_date)
|
|
|
|
expect(logs_in_range).to include(recent_log)
|
|
expect(logs_in_range).not_to include(old_log)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "helper methods" do
|
|
describe "#user_display_name" do
|
|
context "with first and last name" do
|
|
let(:log) do
|
|
build(
|
|
:telegram_bot_log,
|
|
telegram_first_name: "John",
|
|
telegram_last_name: "Doe",
|
|
)
|
|
end
|
|
|
|
it "returns full name" do
|
|
expect(log.user_display_name).to eq("John Doe")
|
|
end
|
|
end
|
|
|
|
context "with only first name" do
|
|
let(:log) do
|
|
build(
|
|
:telegram_bot_log,
|
|
telegram_first_name: "John",
|
|
telegram_last_name: nil,
|
|
)
|
|
end
|
|
|
|
it "returns first name" do
|
|
expect(log.user_display_name).to eq("John")
|
|
end
|
|
end
|
|
|
|
context "with only username" do
|
|
let(:log) do
|
|
build(:telegram_bot_log, :username_only, telegram_username: "johndoe")
|
|
end
|
|
|
|
it "returns username with @ prefix" do
|
|
expect(log.user_display_name).to eq("@johndoe")
|
|
end
|
|
end
|
|
|
|
context "with minimal user info" do
|
|
let(:log) do
|
|
build(
|
|
:telegram_bot_log,
|
|
:minimal_user_info,
|
|
telegram_user_id: 123_456,
|
|
)
|
|
end
|
|
|
|
it "returns user ID format" do
|
|
expect(log.user_display_name).to eq("User 123456")
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#has_performance_metrics?" do
|
|
context "with core timing metrics" do
|
|
let(:log) { build(:telegram_bot_log, :successful) }
|
|
|
|
it "returns true" do
|
|
expect(log.has_performance_metrics?).to be true
|
|
end
|
|
end
|
|
|
|
context "with missing download timing" do
|
|
let(:log) { build(:telegram_bot_log, download_time: nil) }
|
|
|
|
it "returns false" do
|
|
expect(log.has_performance_metrics?).to be false
|
|
end
|
|
end
|
|
|
|
context "with missing fingerprint timing" do
|
|
let(:log) do
|
|
build(:telegram_bot_log, fingerprint_computation_time: nil)
|
|
end
|
|
|
|
it "returns false" do
|
|
expect(log.has_performance_metrics?).to be false
|
|
end
|
|
end
|
|
|
|
context "with missing search timing" do
|
|
let(:log) { build(:telegram_bot_log, search_computation_time: nil) }
|
|
|
|
it "returns false" do
|
|
expect(log.has_performance_metrics?).to be false
|
|
end
|
|
end
|
|
|
|
context "with core timings missing" do
|
|
let(:log) { build(:telegram_bot_log, :with_error) }
|
|
|
|
it "returns false" do
|
|
expect(log.has_performance_metrics?).to be false
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#has_complete_performance_metrics?" do
|
|
context "with all timing metrics" do
|
|
let(:log) { build(:telegram_bot_log, :successful) }
|
|
|
|
it "returns true" do
|
|
expect(log.has_complete_performance_metrics?).to be true
|
|
end
|
|
end
|
|
|
|
context "with missing image_processing_time" do
|
|
let(:log) { build(:telegram_bot_log, image_processing_time: nil) }
|
|
|
|
it "returns false" do
|
|
expect(log.has_complete_performance_metrics?).to be false
|
|
end
|
|
end
|
|
|
|
context "with missing any timing" do
|
|
let(:log) { build(:telegram_bot_log, download_time: nil) }
|
|
|
|
it "returns false" do
|
|
expect(log.has_complete_performance_metrics?).to be false
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "database constraints and indexes" do
|
|
it "has proper table name" do
|
|
expect(TelegramBotLog.table_name).to eq("telegram_bot_logs")
|
|
end
|
|
|
|
context "foreign key constraint" do
|
|
let(:blob_file) { create(:blob_file, :image_blob) }
|
|
|
|
it "allows valid BlobFile association" do
|
|
log = build(:telegram_bot_log, processed_image_sha256: blob_file.sha256)
|
|
expect { log.save! }.not_to raise_error
|
|
end
|
|
|
|
it "prevents invalid sha256 reference" do
|
|
invalid_sha256 = "\x00" * 32 # Invalid sha256
|
|
log = build(:telegram_bot_log, processed_image_sha256: invalid_sha256)
|
|
expect { log.save! }.to raise_error(ActiveRecord::InvalidForeignKey)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "factory traits" do
|
|
it "creates successful log with correct attributes" do
|
|
log = build(:telegram_bot_log, :successful)
|
|
expect(log.status).to eq("success")
|
|
expect(log.search_results_count).to be > 0
|
|
expect(log.has_performance_metrics?).to be true
|
|
end
|
|
|
|
it "creates error log with correct attributes" do
|
|
log = build(:telegram_bot_log, :with_error)
|
|
expect(log.status).to eq("error")
|
|
expect(log.search_results_count).to eq(0)
|
|
expect(log.error_message).to be_present
|
|
expect(log.has_performance_metrics?).to be false
|
|
end
|
|
|
|
it "creates log with image association" do
|
|
log = build(:telegram_bot_log, :with_image)
|
|
expect(log.processed_image).to be_present
|
|
expect(log.processed_image.content_type).to eq("image/jpeg")
|
|
end
|
|
|
|
it "creates log with minimal user info" do
|
|
log = build(:telegram_bot_log, :minimal_user_info)
|
|
expect(log.telegram_username).to be_nil
|
|
expect(log.telegram_first_name).to be_nil
|
|
expect(log.telegram_last_name).to be_nil
|
|
expect(log.user_display_name).to match(/User \d+/)
|
|
end
|
|
end
|
|
|
|
describe "realistic usage scenarios" do
|
|
context "successful image search" do
|
|
let(:blob_file) { create(:blob_file, :image_blob) }
|
|
let(:log) do
|
|
create(
|
|
:telegram_bot_log,
|
|
:successful,
|
|
:with_image,
|
|
processed_image: blob_file,
|
|
response_data: {
|
|
matches: 3,
|
|
threshold: 90,
|
|
posts: [
|
|
{ id: 1, similarity: 95.2, url: "https://example.com/1" },
|
|
{ id: 2, similarity: 92.1, url: "https://example.com/2" },
|
|
{ id: 3, similarity: 90.5, url: "https://example.com/3" },
|
|
],
|
|
},
|
|
)
|
|
end
|
|
|
|
it "saves and retrieves correctly" do
|
|
expect(log).to be_persisted
|
|
expect(log.status_success?).to be true
|
|
expect(log.processed_image).to eq(blob_file)
|
|
expect(log.response_data["matches"]).to eq(3)
|
|
expect(log.total_request_time).to be > 0
|
|
end
|
|
end
|
|
|
|
context "failed image processing" do
|
|
let(:log) do
|
|
create(
|
|
:telegram_bot_log,
|
|
:with_error,
|
|
error_message: "Corrupted image file",
|
|
)
|
|
end
|
|
|
|
it "properly records error state" do
|
|
expect(log.status_error?).to be true
|
|
expect(log.search_results_count).to eq(0)
|
|
expect(log.error_message).to eq("Corrupted image file")
|
|
expect(log.has_performance_metrics?).to be false
|
|
end
|
|
end
|
|
end
|
|
end
|