dawarich/spec/models/stat_spec.rb

484 lines
14 KiB
Ruby

# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Stat, type: :model do
describe 'associations' do
it { is_expected.to belong_to(:user) }
it { is_expected.to validate_presence_of(:year) }
it { is_expected.to validate_presence_of(:month) }
end
describe 'methods' do
let(:year) { 2021 }
let(:user) { create(:user) }
describe '#distance_by_day' do
subject { stat.distance_by_day }
let(:user) { create(:user) }
let(:stat) { create(:stat, year:, month: 1, user:) }
let(:expected_distance) do
# 31 day of January
(1..31).map { |day| [day, 0] }
end
context 'when there are points' do
let!(:points) do
create(:point, user:, lonlat: 'POINT(1 1)', timestamp: DateTime.new(year, 1, 1, 1))
create(:point, user:, lonlat: 'POINT(2 2)', timestamp: DateTime.new(year, 1, 1, 2))
end
before { expected_distance[0][1] = 156_876 }
it 'returns distance by day' do
expect(subject).to eq(expected_distance)
end
end
context 'when there are no points' do
it 'returns distance by day' do
expect(subject).to eq(expected_distance)
end
end
end
describe '#timespan' do
subject { stat.send(:timespan) }
let(:stat) { build(:stat, year:, month: 1) }
let(:expected_timespan) { DateTime.new(year, 1).beginning_of_month..DateTime.new(year, 1).end_of_month }
it 'returns timespan' do
expect(subject).to eq(expected_timespan)
end
end
describe '#self.year_distance' do
subject { described_class.year_distance(year, user) }
let(:user) { create(:user) }
let(:expected_distance) do
(1..12).map { |month| [Date::MONTHNAMES[month], 0] }
end
context 'when there are stats' do
let!(:stats) do
create(:stat, year:, month: 1, distance: 100, user:)
create(:stat, year:, month: 2, distance: 200, user:)
end
before do
expected_distance[0][1] = 100
expected_distance[1][1] = 200
end
it 'returns year distance' do
expect(subject).to eq(expected_distance)
end
end
context 'when there are no stats' do
it 'returns year distance' do
expect(subject).to eq(expected_distance)
end
end
end
describe '#points' do
subject { stat.points.to_a }
let(:stat) { create(:stat, year:, month: 1, user:) }
let(:base_timestamp) { DateTime.new(year, 1, 1, 5, 0, 0) }
let!(:points) do
[
create(:point, user:, timestamp: base_timestamp),
create(:point, user:, timestamp: base_timestamp + 1.hour),
create(:point, user:, timestamp: base_timestamp + 2.hours)
]
end
it 'returns points' do
expect(subject).to eq(points)
end
end
describe '#calculate_data_bounds' do
let(:stat) { create(:stat, year: 2024, month: 6, user:) }
let(:user) { create(:user) }
context 'when stat has points' do
before do
# Create test points within the month (June 2024)
create(:point,
user:,
latitude: 40.6,
longitude: -74.1,
timestamp: Time.new(2024, 6, 1, 12, 0).to_i)
create(:point,
user:,
latitude: 40.8,
longitude: -73.9,
timestamp: Time.new(2024, 6, 15, 15, 0).to_i)
create(:point,
user:,
latitude: 40.7,
longitude: -74.0,
timestamp: Time.new(2024, 6, 30, 18, 0).to_i)
# Points outside the month (should be ignored)
create(:point,
user:,
latitude: 41.0,
longitude: -75.0,
timestamp: Time.new(2024, 5, 31, 23, 59).to_i) # May
create(:point,
user:,
latitude: 39.0,
longitude: -72.0,
timestamp: Time.new(2024, 7, 1, 0, 1).to_i) # July
end
it 'returns correct bounding box for points within the month' do
result = stat.calculate_data_bounds
expect(result).to be_a(Hash)
expect(result[:min_lat]).to eq(40.6)
expect(result[:max_lat]).to eq(40.8)
expect(result[:min_lng]).to eq(-74.1)
expect(result[:max_lng]).to eq(-73.9)
expect(result[:point_count]).to eq(3)
end
context 'with points from different users' do
let(:other_user) { create(:user) }
before do
# Add points from a different user (should be ignored)
create(:point,
user: other_user,
latitude: 50.0,
longitude: -80.0,
timestamp: Time.new(2024, 6, 15, 12, 0).to_i)
end
it 'only includes points from the stat user' do
result = stat.calculate_data_bounds
expect(result[:min_lat]).to eq(40.6)
expect(result[:max_lat]).to eq(40.8)
expect(result[:min_lng]).to eq(-74.1)
expect(result[:max_lng]).to eq(-73.9)
expect(result[:point_count]).to eq(3) # Still only 3 points from the stat user
end
end
context 'with single point' do
let(:single_point_user) { create(:user) }
let(:single_point_stat) { create(:stat, year: 2024, month: 7, user: single_point_user) }
before do
create(:point,
user: single_point_user,
latitude: 45.5,
longitude: -122.65,
timestamp: Time.new(2024, 7, 15, 14, 30).to_i)
end
it 'returns bounds with same min and max values' do
result = single_point_stat.calculate_data_bounds
expect(result[:min_lat]).to eq(45.5)
expect(result[:max_lat]).to eq(45.5)
expect(result[:min_lng]).to eq(-122.65)
expect(result[:max_lng]).to eq(-122.65)
expect(result[:point_count]).to eq(1)
end
end
context 'with edge case coordinates' do
let(:edge_user) { create(:user) }
let(:edge_stat) { create(:stat, year: 2024, month: 8, user: edge_user) }
before do
# Test with extreme coordinate values
create(:point,
user: edge_user,
latitude: -90.0, # South Pole
longitude: -180.0, # Date Line West
timestamp: Time.new(2024, 8, 1, 0, 0).to_i)
create(:point,
user: edge_user,
latitude: 90.0, # North Pole
longitude: 180.0, # Date Line East
timestamp: Time.new(2024, 8, 31, 23, 59).to_i)
end
it 'handles extreme coordinate values correctly' do
result = edge_stat.calculate_data_bounds
expect(result[:min_lat]).to eq(-90.0)
expect(result[:max_lat]).to eq(90.0)
expect(result[:min_lng]).to eq(-180.0)
expect(result[:max_lng]).to eq(180.0)
expect(result[:point_count]).to eq(2)
end
end
end
context 'when stat has no points' do
let(:empty_user) { create(:user) }
let(:empty_stat) { create(:stat, year: 2024, month: 10, user: empty_user) }
it 'returns nil' do
result = empty_stat.calculate_data_bounds
expect(result).to be_nil
end
end
context 'when stat has points but none within the month timeframe' do
let(:empty_month_user) { create(:user) }
let(:empty_month_stat) { create(:stat, year: 2024, month: 9, user: empty_month_user) }
before do
# Create points outside the target month
create(:point,
user: empty_month_user,
latitude: 40.7,
longitude: -74.0,
timestamp: Time.new(2024, 8, 31, 23, 59).to_i) # August
create(:point,
user: empty_month_user,
latitude: 40.8,
longitude: -73.9,
timestamp: Time.new(2024, 10, 1, 0, 1).to_i) # October
end
it 'returns nil when no points exist in the month' do
result = empty_month_stat.calculate_data_bounds
expect(result).to be_nil
end
end
end
describe 'sharing settings' do
let(:user) { create(:user) }
let(:stat) { create(:stat, year: 2024, month: 6, user: user) }
describe '#sharing_enabled?' do
context 'when sharing_settings is nil' do
before { stat.update_column(:sharing_settings, nil) }
it 'returns false' do
expect(stat.sharing_enabled?).to be false
end
end
context 'when sharing_settings is empty hash' do
before { stat.update(sharing_settings: {}) }
it 'returns false' do
expect(stat.sharing_enabled?).to be false
end
end
context 'when enabled is false' do
before { stat.update(sharing_settings: { 'enabled' => false }) }
it 'returns false' do
expect(stat.sharing_enabled?).to be false
end
end
context 'when enabled is true' do
before { stat.update(sharing_settings: { 'enabled' => true }) }
it 'returns true' do
expect(stat.sharing_enabled?).to be true
end
end
context 'when enabled is a string "true"' do
before { stat.update(sharing_settings: { 'enabled' => 'true' }) }
it 'returns false (strict boolean check)' do
expect(stat.sharing_enabled?).to be false
end
end
end
describe '#sharing_expired?' do
context 'when sharing_settings is nil' do
before { stat.update_column(:sharing_settings, nil) }
it 'returns false' do
expect(stat.sharing_expired?).to be false
end
end
context 'when expiration is blank' do
before { stat.update(sharing_settings: { 'enabled' => true }) }
it 'returns false' do
expect(stat.sharing_expired?).to be false
end
end
context 'when expiration is present but expires_at is blank' do
before do
stat.update(sharing_settings: {
'enabled' => true,
'expiration' => '1h'
})
end
it 'returns true' do
expect(stat.sharing_expired?).to be true
end
end
context 'when expires_at is in the future' do
before do
stat.update(sharing_settings: {
'enabled' => true,
'expiration' => '1h',
'expires_at' => 1.hour.from_now.iso8601
})
end
it 'returns false' do
expect(stat.sharing_expired?).to be false
end
end
context 'when expires_at is in the past' do
before do
stat.update(sharing_settings: {
'enabled' => true,
'expiration' => '1h',
'expires_at' => 1.hour.ago.iso8601
})
end
it 'returns true' do
expect(stat.sharing_expired?).to be true
end
end
context 'when expires_at is 1 second in the future' do
before do
stat.update(sharing_settings: {
'enabled' => true,
'expiration' => '1h',
'expires_at' => 1.second.from_now.iso8601
})
end
it 'returns false (not yet expired)' do
expect(stat.sharing_expired?).to be false
end
end
context 'when expires_at is invalid date string' do
before do
stat.update(sharing_settings: {
'enabled' => true,
'expiration' => '1h',
'expires_at' => 'invalid-date'
})
end
it 'returns true (treats as expired)' do
expect(stat.sharing_expired?).to be true
end
end
context 'when expires_at is nil' do
before do
stat.update(sharing_settings: {
'enabled' => true,
'expiration' => '1h',
'expires_at' => nil
})
end
it 'returns true' do
expect(stat.sharing_expired?).to be true
end
end
context 'when expires_at is empty string' do
before do
stat.update(sharing_settings: {
'enabled' => true,
'expiration' => '1h',
'expires_at' => ''
})
end
it 'returns true' do
expect(stat.sharing_expired?).to be true
end
end
end
describe '#public_accessible?' do
context 'when sharing_settings is nil' do
before { stat.update_column(:sharing_settings, nil) }
it 'returns false' do
expect(stat.public_accessible?).to be false
end
end
context 'when sharing is not enabled' do
before { stat.update(sharing_settings: { 'enabled' => false }) }
it 'returns false' do
expect(stat.public_accessible?).to be false
end
end
context 'when sharing is enabled but expired' do
before do
stat.update(sharing_settings: {
'enabled' => true,
'expiration' => '1h',
'expires_at' => 1.hour.ago.iso8601
})
end
it 'returns false' do
expect(stat.public_accessible?).to be false
end
end
context 'when sharing is enabled and not expired' do
before do
stat.update(sharing_settings: {
'enabled' => true,
'expiration' => '1h',
'expires_at' => 1.hour.from_now.iso8601
})
end
it 'returns true' do
expect(stat.public_accessible?).to be true
end
end
context 'when sharing is enabled with no expiration' do
before do
stat.update(sharing_settings: { 'enabled' => true })
end
it 'returns true' do
expect(stat.public_accessible?).to be true
end
end
end
end
end
end