telegram bot logs, first pass
This commit is contained in:
267
spec/controllers/telegram_bot_logs_controller_spec.rb
Normal file
267
spec/controllers/telegram_bot_logs_controller_spec.rb
Normal file
@@ -0,0 +1,267 @@
|
||||
# typed: false
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe TelegramBotLogsController, type: :controller do
|
||||
render_views
|
||||
|
||||
let(:admin_user) { create(:user, role: :admin) }
|
||||
let(:regular_user) { create(:user, role: :user) }
|
||||
let(:telegram_bot_log) { create(:telegram_bot_log, :successful, :with_image) }
|
||||
|
||||
describe "authorization" do
|
||||
context "when user is not signed in" do
|
||||
describe "GET #index" do
|
||||
it "redirects to sign in" do
|
||||
get :index
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET #show" do
|
||||
it "redirects to sign in" do
|
||||
get :show, params: { id: telegram_bot_log.id }
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when regular user is signed in" do
|
||||
before { sign_in regular_user }
|
||||
|
||||
describe "GET #index" do
|
||||
it "redirects to root with access denied" do
|
||||
get :index
|
||||
expect(response).to redirect_to(root_path)
|
||||
expect(flash[:alert]).to eq(
|
||||
"You are not authorized to perform this action.",
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET #show" do
|
||||
it "redirects to root with access denied" do
|
||||
get :show, params: { id: telegram_bot_log.id }
|
||||
expect(response).to redirect_to(root_path)
|
||||
expect(flash[:alert]).to eq(
|
||||
"You are not authorized to perform this action.",
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when admin user is signed in" do
|
||||
before { sign_in admin_user }
|
||||
|
||||
describe "GET #index" do
|
||||
it "returns a success response" do
|
||||
get :index
|
||||
expect(response).to be_successful
|
||||
expect(response).to render_template(:index)
|
||||
end
|
||||
|
||||
it "assigns telegram_bot_logs with proper pagination" do
|
||||
logs = create_list(:telegram_bot_log, 3, :successful)
|
||||
get :index
|
||||
expect(assigns(:telegram_bot_logs)).to be_present
|
||||
expect(assigns(:telegram_bot_logs)).to respond_to(:current_page)
|
||||
expect(assigns(:telegram_bot_logs).to_a).to match_array(logs)
|
||||
end
|
||||
|
||||
it "respects limit parameter" do
|
||||
create_list(:telegram_bot_log, 5, :successful)
|
||||
get :index, params: { limit: 2 }
|
||||
expect(assigns(:limit)).to eq(2)
|
||||
expect(assigns(:telegram_bot_logs).count).to eq(2)
|
||||
end
|
||||
|
||||
it "clamps limit parameter within bounds" do
|
||||
get :index, params: { limit: 1000 }
|
||||
expect(assigns(:limit)).to eq(500) # max limit
|
||||
|
||||
get :index, params: { limit: 0 }
|
||||
expect(assigns(:limit)).to eq(1) # min limit
|
||||
end
|
||||
|
||||
it "handles pagination parameters" do
|
||||
create_list(:telegram_bot_log, 10, :successful)
|
||||
get :index, params: { page: 2, limit: 5 }
|
||||
expect(assigns(:telegram_bot_logs).current_page).to eq(2)
|
||||
end
|
||||
|
||||
it "assigns status options for filtering" do
|
||||
get :index
|
||||
expect(assigns(:status_options)).to eq(TelegramBotLog.statuses.keys)
|
||||
end
|
||||
|
||||
it "assigns filter params" do
|
||||
filter_params = {
|
||||
telegram_user_id: "123456789",
|
||||
status: "successful",
|
||||
start_date: "2023-01-01",
|
||||
end_date: "2023-12-31",
|
||||
min_results: "1",
|
||||
max_results: "10",
|
||||
slow_requests: "true",
|
||||
}
|
||||
|
||||
get :index, params: filter_params
|
||||
assigned_params = assigns(:filter_params)
|
||||
expect(assigned_params["telegram_user_id"]).to eq("123456789")
|
||||
expect(assigned_params["status"]).to eq("successful")
|
||||
expect(assigned_params["start_date"]).to eq("2023-01-01")
|
||||
expect(assigned_params["end_date"]).to eq("2023-12-31")
|
||||
expect(assigned_params["min_results"]).to eq("1")
|
||||
expect(assigned_params["max_results"]).to eq("10")
|
||||
expect(assigned_params["slow_requests"]).to eq("true")
|
||||
end
|
||||
|
||||
describe "filtering" do
|
||||
let!(:user_123_log) do
|
||||
create(:telegram_bot_log, :successful, telegram_user_id: 123_456_789)
|
||||
end
|
||||
let!(:user_456_log) do
|
||||
create(:telegram_bot_log, :successful, telegram_user_id: 456_789_123)
|
||||
end
|
||||
let!(:error_log) { create(:telegram_bot_log, :with_error) }
|
||||
let!(:no_results_log) { create(:telegram_bot_log, :no_results) }
|
||||
|
||||
it "filters by telegram_user_id" do
|
||||
get :index, params: { telegram_user_id: "123456789" }
|
||||
logs = assigns(:telegram_bot_logs).to_a
|
||||
expect(logs).to include(user_123_log)
|
||||
expect(logs).not_to include(user_456_log)
|
||||
end
|
||||
|
||||
it "filters by status" do
|
||||
get :index, params: { status: "error" }
|
||||
logs = assigns(:telegram_bot_logs).to_a
|
||||
expect(logs).to include(error_log)
|
||||
expect(logs).not_to include(user_123_log)
|
||||
end
|
||||
|
||||
it "filters by search results count range" do
|
||||
high_results_log =
|
||||
create(:telegram_bot_log, :successful, search_results_count: 5)
|
||||
low_results_log =
|
||||
create(:telegram_bot_log, :successful, search_results_count: 1)
|
||||
|
||||
get :index, params: { min_results: "3" }
|
||||
logs = assigns(:telegram_bot_logs).to_a
|
||||
expect(logs).to include(high_results_log)
|
||||
expect(logs).not_to include(low_results_log)
|
||||
end
|
||||
|
||||
it "filters by date range" do
|
||||
old_log =
|
||||
create(
|
||||
:telegram_bot_log,
|
||||
:successful,
|
||||
request_timestamp: 1.week.ago,
|
||||
)
|
||||
new_log =
|
||||
create(:telegram_bot_log, :successful, request_timestamp: 1.day.ago)
|
||||
|
||||
get :index,
|
||||
params: {
|
||||
start_date: 3.days.ago.to_date.to_s,
|
||||
end_date: Date.current.to_s,
|
||||
}
|
||||
logs = assigns(:telegram_bot_logs).to_a
|
||||
expect(logs).to include(new_log)
|
||||
expect(logs).not_to include(old_log)
|
||||
end
|
||||
|
||||
it "filters slow requests" do
|
||||
fast_log =
|
||||
create(
|
||||
:telegram_bot_log,
|
||||
:successful,
|
||||
fingerprint_computation_time: 0.1,
|
||||
search_computation_time: 0.2,
|
||||
)
|
||||
slow_log =
|
||||
create(
|
||||
:telegram_bot_log,
|
||||
:successful,
|
||||
fingerprint_computation_time: 0.8,
|
||||
search_computation_time: 0.9,
|
||||
)
|
||||
|
||||
get :index, params: { slow_requests: "true" }
|
||||
logs = assigns(:telegram_bot_logs).to_a
|
||||
expect(logs).to include(slow_log)
|
||||
expect(logs).not_to include(fast_log)
|
||||
end
|
||||
end
|
||||
|
||||
describe "includes associations" do
|
||||
it "includes processed_image association" do
|
||||
create(:telegram_bot_log, :with_image)
|
||||
get :index
|
||||
|
||||
# Verify the association is loaded
|
||||
expect(
|
||||
assigns(:telegram_bot_logs).first&.processed_image,
|
||||
).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
describe "error handling" do
|
||||
it "handles invalid filter parameters gracefully" do
|
||||
get :index,
|
||||
params: {
|
||||
start_date: "invalid-date",
|
||||
min_results: "not-a-number",
|
||||
}
|
||||
expect(response).to be_successful
|
||||
expect(assigns(:telegram_bot_logs)).not_to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET #show" do
|
||||
let(:telegram_bot_log) do
|
||||
create(:telegram_bot_log, :successful, :with_image)
|
||||
end
|
||||
|
||||
it "returns a success response" do
|
||||
get :show, params: { id: telegram_bot_log.id }
|
||||
expect(response).to be_successful
|
||||
expect(response).to render_template(:show)
|
||||
end
|
||||
|
||||
it "assigns the requested telegram_bot_log" do
|
||||
get :show, params: { id: telegram_bot_log.id }
|
||||
expect(assigns(:telegram_bot_log)).to eq(telegram_bot_log)
|
||||
end
|
||||
|
||||
it "includes processed_image association" do
|
||||
get :show, params: { id: telegram_bot_log.id }
|
||||
|
||||
# Verify the association is loaded
|
||||
expect(assigns(:telegram_bot_log).processed_image).to be_present
|
||||
end
|
||||
|
||||
it "handles non-existent log gracefully" do
|
||||
get :show, params: { id: 999_999 }
|
||||
expect(response).to redirect_to(telegram_bot_logs_path)
|
||||
expect(flash[:alert]).to eq("Telegram bot log not found.")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "performance" do
|
||||
context "with many records" do
|
||||
before { create_list(:telegram_bot_log, 100, :successful) }
|
||||
|
||||
it "loads records successfully with many logs" do
|
||||
sign_in admin_user
|
||||
|
||||
get :index, params: { limit: 50 }
|
||||
expect(response).to be_successful
|
||||
expect(assigns(:telegram_bot_logs).count).to eq(50)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
97
spec/factories/telegram_bot_logs.rb
Normal file
97
spec/factories/telegram_bot_logs.rb
Normal file
@@ -0,0 +1,97 @@
|
||||
# typed: false
|
||||
FactoryBot.define do
|
||||
factory :telegram_bot_log do
|
||||
sequence(:telegram_user_id) { |n| 100_000_000 + n }
|
||||
sequence(:telegram_chat_id) { |n| 200_000_000 + n }
|
||||
telegram_username { "user#{SecureRandom.alphanumeric(6)}" }
|
||||
telegram_first_name { "Test" }
|
||||
telegram_last_name { "User" }
|
||||
request_timestamp { Time.current }
|
||||
status { :success }
|
||||
search_results_count { 3 }
|
||||
download_time { 0.08 }
|
||||
image_processing_time { 0.05 }
|
||||
fingerprint_computation_time { 0.15 }
|
||||
search_computation_time { 0.25 }
|
||||
total_request_time { 0.53 }
|
||||
response_data { { matches: 3, threshold: 90 } }
|
||||
|
||||
trait :with_image do
|
||||
association :processed_image, factory: %i[blob_file image_blob]
|
||||
end
|
||||
|
||||
trait :successful do
|
||||
status { :success }
|
||||
search_results_count { 5 }
|
||||
download_time { 0.06 }
|
||||
image_processing_time { 0.04 }
|
||||
fingerprint_computation_time { 0.12 }
|
||||
search_computation_time { 0.18 }
|
||||
total_request_time { 0.40 }
|
||||
response_data { { matches: 5, threshold: 90, posts: [1, 2, 3, 4, 5] } }
|
||||
end
|
||||
|
||||
trait :with_no_results do
|
||||
status { :no_results }
|
||||
search_results_count { 0 }
|
||||
download_time { 0.07 }
|
||||
image_processing_time { 0.03 }
|
||||
fingerprint_computation_time { 0.10 }
|
||||
search_computation_time { 0.05 }
|
||||
total_request_time { 0.25 }
|
||||
response_data { { matches: 0, threshold: 90 } }
|
||||
end
|
||||
|
||||
trait :with_error do
|
||||
status { :error }
|
||||
search_results_count { 0 }
|
||||
download_time { 0.05 }
|
||||
image_processing_time { nil }
|
||||
fingerprint_computation_time { nil }
|
||||
search_computation_time { nil }
|
||||
total_request_time { 0.15 }
|
||||
error_message { "Failed to process image: Invalid format" }
|
||||
response_data { { error: "Invalid image format" } }
|
||||
end
|
||||
|
||||
trait :invalid_image do
|
||||
status { :invalid_image }
|
||||
search_results_count { 0 }
|
||||
download_time { 0.04 }
|
||||
image_processing_time { nil }
|
||||
fingerprint_computation_time { nil }
|
||||
search_computation_time { nil }
|
||||
total_request_time { 0.12 }
|
||||
error_message { "Image format not supported" }
|
||||
response_data { { error: "Unsupported format" } }
|
||||
end
|
||||
|
||||
trait :minimal_user_info do
|
||||
telegram_username { nil }
|
||||
telegram_first_name { nil }
|
||||
telegram_last_name { nil }
|
||||
end
|
||||
|
||||
trait :username_only do
|
||||
telegram_first_name { nil }
|
||||
telegram_last_name { nil }
|
||||
telegram_username { "anonymous_user" }
|
||||
end
|
||||
|
||||
trait :slow_processing do
|
||||
download_time { 0.5 }
|
||||
image_processing_time { 0.3 }
|
||||
fingerprint_computation_time { 2.5 }
|
||||
search_computation_time { 1.8 }
|
||||
total_request_time { 5.1 }
|
||||
end
|
||||
|
||||
trait :fast_processing do
|
||||
download_time { 0.01 }
|
||||
image_processing_time { 0.005 }
|
||||
fingerprint_computation_time { 0.02 }
|
||||
search_computation_time { 0.01 }
|
||||
total_request_time { 0.045 }
|
||||
end
|
||||
end
|
||||
end
|
||||
108
spec/lib/stopwatch_spec.rb
Normal file
108
spec/lib/stopwatch_spec.rb
Normal file
@@ -0,0 +1,108 @@
|
||||
# typed: false
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe Stopwatch do
|
||||
describe ".start" do
|
||||
it "creates a new stopwatch with current time as start time" do
|
||||
before_time = Time.current
|
||||
stopwatch = Stopwatch.start
|
||||
after_time = Time.current
|
||||
|
||||
expect(stopwatch.start_time).to be_between(before_time, after_time)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#elapsed" do
|
||||
it "calculates elapsed time correctly" do
|
||||
start_time = Time.parse("2023-01-01 10:00:00")
|
||||
stopwatch = Stopwatch.new(start_time)
|
||||
|
||||
# Mock Time.current for this specific stopwatch instance
|
||||
allow(stopwatch).to receive(:elapsed) { 2.5 }
|
||||
|
||||
expect(stopwatch.elapsed).to eq(2.5)
|
||||
end
|
||||
|
||||
it "returns non-negative elapsed time" do
|
||||
stopwatch = Stopwatch.start
|
||||
expect(stopwatch.elapsed).to be >= 0
|
||||
end
|
||||
end
|
||||
|
||||
describe "#elapsed_ms" do
|
||||
it "formats milliseconds correctly" do
|
||||
stopwatch = Stopwatch.start
|
||||
allow(stopwatch).to receive(:elapsed) { 1.2345 }
|
||||
|
||||
expect(stopwatch.elapsed_ms).to eq("1234.5ms")
|
||||
end
|
||||
|
||||
it "handles very small times" do
|
||||
stopwatch = Stopwatch.start
|
||||
allow(stopwatch).to receive(:elapsed) { 0.001 }
|
||||
|
||||
expect(stopwatch.elapsed_ms).to eq("1.0ms")
|
||||
end
|
||||
end
|
||||
|
||||
describe "#elapsed_s" do
|
||||
it "formats seconds with 3 decimal places" do
|
||||
stopwatch = Stopwatch.start
|
||||
allow(stopwatch).to receive(:elapsed) { 1.2345 }
|
||||
|
||||
expect(stopwatch.elapsed_s).to eq("1.234s")
|
||||
end
|
||||
|
||||
it "formats small times to 3 decimal places" do
|
||||
stopwatch = Stopwatch.start
|
||||
allow(stopwatch).to receive(:elapsed) { 0.001234 }
|
||||
|
||||
expect(stopwatch.elapsed_s).to eq("0.001s")
|
||||
end
|
||||
|
||||
it "formats zero time correctly" do
|
||||
stopwatch = Stopwatch.start
|
||||
allow(stopwatch).to receive(:elapsed) { 0.0 }
|
||||
|
||||
expect(stopwatch.elapsed_s).to eq("0.000s")
|
||||
end
|
||||
end
|
||||
|
||||
describe "#start_time" do
|
||||
it "returns the time when the stopwatch was started" do
|
||||
start_time = Time.parse("2023-01-01 10:00:00")
|
||||
stopwatch = Stopwatch.new(start_time)
|
||||
|
||||
expect(stopwatch.start_time).to eq(start_time)
|
||||
end
|
||||
end
|
||||
|
||||
describe "practical usage" do
|
||||
it "provides the expected API usage pattern" do
|
||||
# Test the exact usage pattern the user requested
|
||||
download_time = Stopwatch.start
|
||||
|
||||
# The elapsed method should return a float representing seconds
|
||||
duration = download_time.elapsed
|
||||
expect(duration).to be_a(Float)
|
||||
expect(duration).to be >= 0
|
||||
|
||||
# Should provide convenient formatting methods
|
||||
expect(download_time.elapsed_s).to match(/^\d+\.\d{3}s$/)
|
||||
expect(download_time.elapsed_ms).to match(/^\d+\.\d+ms$/)
|
||||
end
|
||||
|
||||
it "can be used for timing operations" do
|
||||
# Simulate timing an operation
|
||||
operation_timer = Stopwatch.start
|
||||
|
||||
# Simulate work (just check the timing interface works)
|
||||
# In real usage: do_some_work()
|
||||
sleep(0.001) # Minimal sleep to ensure some time passes
|
||||
|
||||
execution_time = operation_timer.elapsed
|
||||
expect(execution_time).to be > 0
|
||||
expect(execution_time).to be < 1 # Should be very fast
|
||||
end
|
||||
end
|
||||
end
|
||||
466
spec/models/telegram_bot_log_spec.rb
Normal file
466
spec/models/telegram_bot_log_spec.rb
Normal file
@@ -0,0 +1,466 @@
|
||||
# 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
|
||||
68
spec/policies/telegram_bot_log_policy_spec.rb
Normal file
68
spec/policies/telegram_bot_log_policy_spec.rb
Normal file
@@ -0,0 +1,68 @@
|
||||
# typed: false
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe TelegramBotLogPolicy, type: :policy do
|
||||
let(:admin_user) { create(:user, role: :admin) }
|
||||
let(:regular_user) { create(:user, role: :user) }
|
||||
let(:telegram_bot_log) { create(:telegram_bot_log, :successful) }
|
||||
|
||||
describe "permissions" do
|
||||
describe "index?" do
|
||||
it "grants access for admin users" do
|
||||
policy = TelegramBotLogPolicy.new(admin_user, TelegramBotLog)
|
||||
expect(policy.index?).to be true
|
||||
end
|
||||
|
||||
it "denies access for regular users" do
|
||||
policy = TelegramBotLogPolicy.new(regular_user, TelegramBotLog)
|
||||
expect(policy.index?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe "show?" do
|
||||
it "grants access for admin users" do
|
||||
policy = TelegramBotLogPolicy.new(admin_user, telegram_bot_log)
|
||||
expect(policy.show?).to be true
|
||||
end
|
||||
|
||||
it "denies access for regular users" do
|
||||
policy = TelegramBotLogPolicy.new(regular_user, telegram_bot_log)
|
||||
expect(policy.show?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe "other actions" do
|
||||
it "denies new/create/edit/update/destroy for all users" do
|
||||
admin_policy = TelegramBotLogPolicy.new(admin_user, telegram_bot_log)
|
||||
regular_policy =
|
||||
TelegramBotLogPolicy.new(regular_user, telegram_bot_log)
|
||||
|
||||
# These methods are inherited from ApplicationPolicy and should be false
|
||||
expect(admin_policy.create?).to be false
|
||||
expect(admin_policy.new?).to be false
|
||||
expect(admin_policy.edit?).to be false
|
||||
expect(admin_policy.update?).to be false
|
||||
expect(admin_policy.destroy?).to be false
|
||||
|
||||
expect(regular_policy.create?).to be false
|
||||
expect(regular_policy.new?).to be false
|
||||
expect(regular_policy.edit?).to be false
|
||||
expect(regular_policy.update?).to be false
|
||||
expect(regular_policy.destroy?).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "scope" do
|
||||
let!(:log1) { create(:telegram_bot_log, :successful) }
|
||||
let!(:log2) { create(:telegram_bot_log, :with_error) }
|
||||
|
||||
it "returns all logs for admin users" do
|
||||
scope = Pundit.policy_scope(admin_user, TelegramBotLog)
|
||||
expect(scope).to include(log1, log2)
|
||||
end
|
||||
|
||||
# Note: The policy scope returns scope.all for all users, but actual access control
|
||||
# is handled at the controller level via authorize calls
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user