mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-11 01:31:39 -05:00
Refactor: Apply Rails best practices to trip sharing implementation
- Remove unused @is_public_view variable from controller - Simplify conditionals by leveraging methods that return [] when empty - Move public view from trips/public_show to shared/trips/show (Rails conventions) - Refactor trips#update to be HTML-only (remove JSON responses) - Convert sharing form to use proper Rails form helpers - Move JS controller to shared/ subdirectory with proper namespacing - Create RSpec shared examples for Shareable concern to eliminate duplication - Update request specs to match HTML-only controller behavior - Apply 'render/redirect ... and return' pattern for early returns
This commit is contained in:
parent
429f90e666
commit
b1cbb5555f
10 changed files with 263 additions and 635 deletions
|
|
@ -7,11 +7,8 @@ class Shared::TripsController < ApplicationController
|
|||
redirect_to root_path, alert: 'Shared trip not found or no longer available' and return unless @trip&.public_accessible?
|
||||
|
||||
@user = @trip.user
|
||||
@is_public_view = true
|
||||
@coordinates = @trip.path.present? ? extract_coordinates : []
|
||||
@photo_previews = @trip.share_photos? ? fetch_photo_previews : []
|
||||
|
||||
render 'trips/public_show'
|
||||
@coordinates = extract_coordinates
|
||||
@photo_previews = fetch_photo_previews
|
||||
end
|
||||
|
||||
private
|
||||
|
|
@ -24,6 +21,8 @@ class Shared::TripsController < ApplicationController
|
|||
end
|
||||
|
||||
def fetch_photo_previews
|
||||
return [] unless @trip.share_photos?
|
||||
|
||||
Rails.cache.fetch("trip_photos_#{@trip.id}", expires_in: 1.day) do
|
||||
@trip.photo_previews
|
||||
end
|
||||
|
|
|
|||
|
|
@ -39,8 +39,8 @@ class TripsController < ApplicationController
|
|||
end
|
||||
|
||||
def update
|
||||
# Handle sharing settings update (JSON response)
|
||||
update_sharing and return if params[:sharing]
|
||||
# Handle sharing settings update
|
||||
handle_sharing_update if params[:sharing]
|
||||
|
||||
# Handle regular trip update
|
||||
if @trip.update(trip_params)
|
||||
|
|
@ -68,38 +68,18 @@ class TripsController < ApplicationController
|
|||
).map { [_1.to_f, _2.to_f, _3.to_s, _4.to_s, _5.to_s, _6.to_s, _7.to_s, _8.to_s] }
|
||||
end
|
||||
|
||||
def update_sharing
|
||||
def handle_sharing_update
|
||||
if params[:sharing][:enabled] == '1'
|
||||
sharing_options = {
|
||||
expiration: params[:sharing][:expiration] || '24h'
|
||||
expiration: params[:sharing][:expiration] || '24h',
|
||||
share_notes: params[:sharing][:share_notes] == '1',
|
||||
share_photos: params[:sharing][:share_photos] == '1'
|
||||
}
|
||||
|
||||
# Add optional sharing settings
|
||||
sharing_options[:share_notes] = params[:sharing][:share_notes] == '1'
|
||||
sharing_options[:share_photos] = params[:sharing][:share_photos] == '1'
|
||||
|
||||
@trip.enable_sharing!(**sharing_options)
|
||||
sharing_url = shared_trip_url(@trip.sharing_uuid)
|
||||
|
||||
render json: {
|
||||
success: true,
|
||||
sharing_url: sharing_url,
|
||||
message: 'Sharing enabled successfully'
|
||||
}
|
||||
else
|
||||
@trip.disable_sharing!
|
||||
|
||||
render json: {
|
||||
success: true,
|
||||
message: 'Sharing disabled successfully'
|
||||
}
|
||||
end
|
||||
rescue StandardError => e
|
||||
render json: {
|
||||
success: false,
|
||||
message: 'Failed to update sharing settings',
|
||||
error: e.message
|
||||
}, status: :unprocessable_content
|
||||
end
|
||||
|
||||
def trip_params
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import L from "leaflet";
|
||||
import BaseController from "./base_controller";
|
||||
import BaseController from "../base_controller";
|
||||
|
||||
export default class extends BaseController {
|
||||
static values = {
|
||||
|
|
@ -54,9 +54,9 @@
|
|||
<% if @coordinates.present? %>
|
||||
<div id="public-trip-map"
|
||||
class="w-full h-full"
|
||||
data-controller="public-trip-map"
|
||||
data-public-trip-map-coordinates-value="<%= @coordinates.to_json %>"
|
||||
data-public-trip-map-name-value="<%= @trip.name %>"></div>
|
||||
data-controller="shared--trip-map"
|
||||
data-shared--trip-map-coordinates-value="<%= @coordinates.to_json %>"
|
||||
data-shared--trip-map-name-value="<%= @trip.name %>"></div>
|
||||
<% else %>
|
||||
<div class="flex items-center justify-center h-full">
|
||||
<div class="text-center">
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
Generate a public link to share this trip with others.
|
||||
</p>
|
||||
|
||||
<div id="sharing-controls-<%= trip.id %>" data-trip-id="<%= trip.id %>">
|
||||
<%= form_with model: trip, url: trip_path(trip), method: :patch, data: { turbo: false } do |f| %>
|
||||
<% if trip.sharing_enabled? %>
|
||||
<!-- Sharing is enabled -->
|
||||
<div class="space-y-4">
|
||||
|
|
@ -73,16 +73,11 @@
|
|||
|
||||
<!-- Action Buttons -->
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
class="btn btn-warning flex-1"
|
||||
onclick="regenerateTripSharingLink(<%= trip.id %>)">
|
||||
Regenerate Link
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-error flex-1"
|
||||
onclick="disableTripSharing(<%= trip.id %>)">
|
||||
Disable Sharing
|
||||
</button>
|
||||
<%= f.submit "Disable Sharing",
|
||||
name: 'sharing[enabled]',
|
||||
value: '0',
|
||||
class: "btn btn-error flex-1",
|
||||
data: { turbo_confirm: "Are you sure you want to disable sharing for this trip?" } %>
|
||||
</div>
|
||||
</div>
|
||||
<% else %>
|
||||
|
|
@ -92,118 +87,40 @@
|
|||
<label class="label">
|
||||
<span class="label-text">Expiration</span>
|
||||
</label>
|
||||
<select class="select select-bordered" id="expiration-<%= trip.id %>">
|
||||
<option value="1h">1 hour</option>
|
||||
<option value="12h">12 hours</option>
|
||||
<option value="24h" selected>24 hours</option>
|
||||
<option value="permanent">Never (permanent)</option>
|
||||
</select>
|
||||
<%= f.select 'sharing[expiration]',
|
||||
options_for_select([
|
||||
['1 hour', '1h'],
|
||||
['12 hours', '12h'],
|
||||
['24 hours', '24h'],
|
||||
['Never (permanent)', 'permanent']
|
||||
], '24h'),
|
||||
{},
|
||||
class: 'select select-bordered' %>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="form-control">
|
||||
<label class="label cursor-pointer">
|
||||
<span class="label-text">Share notes</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="toggle toggle-primary"
|
||||
id="share-notes-<%= trip.id %>"
|
||||
checked>
|
||||
<%= f.check_box 'sharing[share_notes]',
|
||||
{ checked: true, class: 'toggle toggle-primary' },
|
||||
'1', '0' %>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label cursor-pointer">
|
||||
<span class="label-text">Share photos</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="toggle toggle-primary"
|
||||
id="share-photos-<%= trip.id %>"
|
||||
checked>
|
||||
<%= f.check_box 'sharing[share_photos]',
|
||||
{ checked: true, class: 'toggle toggle-primary' },
|
||||
'1', '0' %>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="btn btn-primary w-full"
|
||||
onclick="enableTripSharing(<%= trip.id %>)">
|
||||
Enable Sharing
|
||||
</button>
|
||||
<%= f.hidden_field 'sharing[enabled]', value: '1' %>
|
||||
<%= f.submit "Enable Sharing", class: "btn btn-primary w-full" %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function enableTripSharing(tripId) {
|
||||
const expiration = document.getElementById(`expiration-${tripId}`).value;
|
||||
const shareNotes = document.getElementById(`share-notes-${tripId}`).checked ? '1' : '0';
|
||||
const sharePhotos = document.getElementById(`share-photos-${tripId}`).checked ? '1' : '0';
|
||||
|
||||
fetch(`/trips/${tripId}`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
|
||||
},
|
||||
body: JSON.stringify({
|
||||
sharing: {
|
||||
enabled: '1',
|
||||
expiration: expiration,
|
||||
share_notes: shareNotes,
|
||||
share_photos: sharePhotos
|
||||
}
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Failed to enable sharing: ' + (data.message || 'Unknown error'));
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
alert('Error enabling sharing: ' + error);
|
||||
});
|
||||
}
|
||||
|
||||
function disableTripSharing(tripId) {
|
||||
if (!confirm('Are you sure you want to disable sharing for this trip?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(`/trips/${tripId}`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
|
||||
},
|
||||
body: JSON.stringify({
|
||||
sharing: {
|
||||
enabled: '0'
|
||||
}
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Failed to disable sharing: ' + (data.message || 'Unknown error'));
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
alert('Error disabling sharing: ' + error);
|
||||
});
|
||||
}
|
||||
|
||||
function regenerateTripSharingLink(tripId) {
|
||||
if (!confirm('Regenerating will invalidate the old link. Continue?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This would require a new endpoint or modify the existing one
|
||||
alert('Regenerate link functionality to be implemented');
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,213 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Shareable do
|
||||
let(:user) { create(:user) }
|
||||
let(:trip) do
|
||||
create(:trip, user: user, name: 'Test Trip',
|
||||
started_at: 1.week.ago, ended_at: Time.current)
|
||||
end
|
||||
|
||||
describe '#generate_sharing_uuid' do
|
||||
it 'generates a UUID before create' do
|
||||
new_trip = build(:trip, user: user)
|
||||
expect(new_trip.sharing_uuid).to be_nil
|
||||
new_trip.save!
|
||||
expect(new_trip.sharing_uuid).to be_present
|
||||
expect(new_trip.sharing_uuid).to match(/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#sharing_enabled?' do
|
||||
it 'returns false by default' do
|
||||
expect(trip.sharing_enabled?).to be false
|
||||
end
|
||||
|
||||
it 'returns true when enabled' do
|
||||
trip.update!(sharing_settings: { 'enabled' => true })
|
||||
expect(trip.sharing_enabled?).to be true
|
||||
end
|
||||
|
||||
it 'returns false when disabled' do
|
||||
trip.update!(sharing_settings: { 'enabled' => false })
|
||||
expect(trip.sharing_enabled?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe '#sharing_expired?' do
|
||||
it 'returns false when no expiration is set' do
|
||||
expect(trip.sharing_expired?).to be false
|
||||
end
|
||||
|
||||
it 'returns false when expires_at is in the future' do
|
||||
trip.update!(sharing_settings: {
|
||||
'expiration' => '24h',
|
||||
'expires_at' => 1.hour.from_now.iso8601
|
||||
})
|
||||
expect(trip.sharing_expired?).to be false
|
||||
end
|
||||
|
||||
it 'returns true when expires_at is in the past' do
|
||||
trip.update!(sharing_settings: {
|
||||
'expiration' => '1h',
|
||||
'expires_at' => 1.hour.ago.iso8601
|
||||
})
|
||||
expect(trip.sharing_expired?).to be true
|
||||
end
|
||||
|
||||
it 'returns true when expiration is set but expires_at is missing' do
|
||||
trip.update!(sharing_settings: {
|
||||
'expiration' => '24h',
|
||||
'expires_at' => nil
|
||||
})
|
||||
expect(trip.sharing_expired?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
describe '#public_accessible?' do
|
||||
it 'returns false by default' do
|
||||
expect(trip.public_accessible?).to be false
|
||||
end
|
||||
|
||||
it 'returns true when enabled and not expired' do
|
||||
trip.update!(sharing_settings: {
|
||||
'enabled' => true,
|
||||
'expiration' => '24h',
|
||||
'expires_at' => 1.hour.from_now.iso8601
|
||||
})
|
||||
expect(trip.public_accessible?).to be true
|
||||
end
|
||||
|
||||
it 'returns false when enabled but expired' do
|
||||
trip.update!(sharing_settings: {
|
||||
'enabled' => true,
|
||||
'expiration' => '1h',
|
||||
'expires_at' => 1.hour.ago.iso8601
|
||||
})
|
||||
expect(trip.public_accessible?).to be false
|
||||
end
|
||||
|
||||
it 'returns false when disabled' do
|
||||
trip.update!(sharing_settings: {
|
||||
'enabled' => false,
|
||||
'expiration' => '24h',
|
||||
'expires_at' => 1.hour.from_now.iso8601
|
||||
})
|
||||
expect(trip.public_accessible?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe '#enable_sharing!' do
|
||||
it 'enables sharing with default 24h expiration' do
|
||||
trip.enable_sharing!
|
||||
expect(trip.sharing_enabled?).to be true
|
||||
expect(trip.sharing_settings['expiration']).to eq('24h')
|
||||
expect(trip.sharing_settings['expires_at']).to be_present
|
||||
end
|
||||
|
||||
it 'enables sharing with custom expiration' do
|
||||
trip.enable_sharing!(expiration: '1h')
|
||||
expect(trip.sharing_enabled?).to be true
|
||||
expect(trip.sharing_settings['expiration']).to eq('1h')
|
||||
end
|
||||
|
||||
it 'enables sharing with permanent expiration' do
|
||||
trip.enable_sharing!(expiration: 'permanent')
|
||||
expect(trip.sharing_enabled?).to be true
|
||||
expect(trip.sharing_settings['expiration']).to eq('permanent')
|
||||
expect(trip.sharing_settings['expires_at']).to be_nil
|
||||
end
|
||||
|
||||
it 'defaults to 24h for invalid expiration' do
|
||||
trip.enable_sharing!(expiration: 'invalid')
|
||||
expect(trip.sharing_settings['expiration']).to eq('24h')
|
||||
end
|
||||
|
||||
it 'stores additional options like share_notes' do
|
||||
trip.enable_sharing!(expiration: '24h', share_notes: true)
|
||||
expect(trip.sharing_settings['share_notes']).to be true
|
||||
end
|
||||
|
||||
it 'stores additional options like share_photos' do
|
||||
trip.enable_sharing!(expiration: '24h', share_photos: true)
|
||||
expect(trip.sharing_settings['share_photos']).to be true
|
||||
end
|
||||
|
||||
it 'generates a sharing_uuid if not present' do
|
||||
trip.update_column(:sharing_uuid, nil)
|
||||
trip.enable_sharing!
|
||||
expect(trip.sharing_uuid).to be_present
|
||||
end
|
||||
|
||||
it 'keeps existing sharing_uuid' do
|
||||
original_uuid = trip.sharing_uuid
|
||||
trip.enable_sharing!
|
||||
expect(trip.sharing_uuid).to eq(original_uuid)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#disable_sharing!' do
|
||||
before do
|
||||
trip.enable_sharing!(expiration: '24h')
|
||||
end
|
||||
|
||||
it 'disables sharing' do
|
||||
trip.disable_sharing!
|
||||
expect(trip.sharing_enabled?).to be false
|
||||
end
|
||||
|
||||
it 'clears expiration settings' do
|
||||
trip.disable_sharing!
|
||||
expect(trip.sharing_settings['expiration']).to be_nil
|
||||
expect(trip.sharing_settings['expires_at']).to be_nil
|
||||
end
|
||||
|
||||
it 'keeps the sharing_uuid' do
|
||||
original_uuid = trip.sharing_uuid
|
||||
trip.disable_sharing!
|
||||
expect(trip.sharing_uuid).to eq(original_uuid)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#generate_new_sharing_uuid!' do
|
||||
it 'generates a new UUID' do
|
||||
original_uuid = trip.sharing_uuid
|
||||
trip.generate_new_sharing_uuid!
|
||||
expect(trip.sharing_uuid).not_to eq(original_uuid)
|
||||
expect(trip.sharing_uuid).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
describe '#share_notes?' do
|
||||
it 'returns false by default' do
|
||||
expect(trip.share_notes?).to be false
|
||||
end
|
||||
|
||||
it 'returns true when share_notes is enabled' do
|
||||
trip.update!(sharing_settings: { 'share_notes' => true })
|
||||
expect(trip.share_notes?).to be true
|
||||
end
|
||||
|
||||
it 'returns false when share_notes is disabled' do
|
||||
trip.update!(sharing_settings: { 'share_notes' => false })
|
||||
expect(trip.share_notes?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe '#share_photos?' do
|
||||
it 'returns false by default' do
|
||||
expect(trip.share_photos?).to be false
|
||||
end
|
||||
|
||||
it 'returns true when share_photos is enabled' do
|
||||
trip.update!(sharing_settings: { 'share_photos' => true })
|
||||
expect(trip.share_photos?).to be true
|
||||
end
|
||||
|
||||
it 'returns false when share_photos is disabled' do
|
||||
trip.update!(sharing_settings: { 'share_photos' => false })
|
||||
expect(trip.share_photos?).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -263,222 +263,8 @@ RSpec.describe Stat, type: :model do
|
|||
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
|
||||
describe 'Shareable concern' do
|
||||
it_behaves_like 'shareable'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -156,36 +156,12 @@ RSpec.describe Trip, type: :model do
|
|||
end
|
||||
|
||||
describe 'Shareable concern' do
|
||||
let(:user) { create(:user) }
|
||||
let(:trip) { create(:trip, user: user) }
|
||||
it_behaves_like 'shareable'
|
||||
|
||||
describe 'sharing_uuid generation' do
|
||||
it 'generates a sharing_uuid on create' do
|
||||
new_trip = build(:trip, user: user)
|
||||
expect(new_trip.sharing_uuid).to be_nil
|
||||
new_trip.save!
|
||||
expect(new_trip.sharing_uuid).to be_present
|
||||
end
|
||||
end
|
||||
describe 'Trip-specific sharing' do
|
||||
let(:user) { create(:user) }
|
||||
let(:trip) { create(:trip, user: user) }
|
||||
|
||||
describe '#public_accessible?' do
|
||||
it 'returns false by default' do
|
||||
expect(trip.public_accessible?).to be false
|
||||
end
|
||||
|
||||
it 'returns true when sharing is enabled and not expired' do
|
||||
trip.enable_sharing!(expiration: '24h')
|
||||
expect(trip.public_accessible?).to be true
|
||||
end
|
||||
|
||||
it 'returns false when sharing is disabled' do
|
||||
trip.enable_sharing!(expiration: '24h')
|
||||
trip.disable_sharing!
|
||||
expect(trip.public_accessible?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe '#enable_sharing!' do
|
||||
it 'enables sharing with notes and photos options' do
|
||||
trip.enable_sharing!(expiration: '24h', share_notes: true, share_photos: true)
|
||||
expect(trip.sharing_enabled?).to be true
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ RSpec.describe 'Shared::Trips', type: :request do
|
|||
|
||||
expect(response.body).to include('Test Trip')
|
||||
expect(response.body).to include('Trip Route')
|
||||
expect(response.body).to include('data-controller="public-trip-map"')
|
||||
expect(response.body).to include('data-controller="shared--trip-map"')
|
||||
expect(response.body).to include(trip.sharing_uuid)
|
||||
end
|
||||
|
||||
|
|
@ -117,17 +117,12 @@ RSpec.describe 'Shared::Trips', type: :request do
|
|||
before { sign_in user }
|
||||
|
||||
context 'enabling sharing' do
|
||||
it 'enables sharing and returns success' do
|
||||
it 'enables sharing and redirects to trip' do
|
||||
patch trip_path(trip),
|
||||
params: { sharing: { enabled: '1', expiration: '24h' } },
|
||||
as: :json
|
||||
params: { sharing: { enabled: '1', expiration: '24h' } }
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
|
||||
json_response = JSON.parse(response.body)
|
||||
expect(json_response['success']).to be(true)
|
||||
expect(json_response['sharing_url']).to be_present
|
||||
expect(json_response['message']).to eq('Sharing enabled successfully')
|
||||
expect(response).to redirect_to(trip_path(trip))
|
||||
expect(flash[:notice]).to eq('Trip was successfully updated.')
|
||||
|
||||
trip.reload
|
||||
expect(trip.sharing_enabled?).to be(true)
|
||||
|
|
@ -136,10 +131,9 @@ RSpec.describe 'Shared::Trips', type: :request do
|
|||
|
||||
it 'enables sharing with notes option' do
|
||||
patch trip_path(trip),
|
||||
params: { sharing: { enabled: '1', expiration: '24h', share_notes: '1' } },
|
||||
as: :json
|
||||
params: { sharing: { enabled: '1', expiration: '24h', share_notes: '1' } }
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(response).to redirect_to(trip_path(trip))
|
||||
|
||||
trip.reload
|
||||
expect(trip.sharing_enabled?).to be(true)
|
||||
|
|
@ -148,10 +142,9 @@ RSpec.describe 'Shared::Trips', type: :request do
|
|||
|
||||
it 'enables sharing with photos option' do
|
||||
patch trip_path(trip),
|
||||
params: { sharing: { enabled: '1', expiration: '24h', share_photos: '1' } },
|
||||
as: :json
|
||||
params: { sharing: { enabled: '1', expiration: '24h', share_photos: '1' } }
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(response).to redirect_to(trip_path(trip))
|
||||
|
||||
trip.reload
|
||||
expect(trip.sharing_enabled?).to be(true)
|
||||
|
|
@ -160,10 +153,9 @@ RSpec.describe 'Shared::Trips', type: :request do
|
|||
|
||||
it 'sets custom expiration when provided' do
|
||||
patch trip_path(trip),
|
||||
params: { sharing: { enabled: '1', expiration: '1h' } },
|
||||
as: :json
|
||||
params: { sharing: { enabled: '1', expiration: '1h' } }
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(response).to redirect_to(trip_path(trip))
|
||||
trip.reload
|
||||
expect(trip.sharing_enabled?).to be(true)
|
||||
expect(trip.sharing_settings['expiration']).to eq('1h')
|
||||
|
|
@ -171,10 +163,9 @@ RSpec.describe 'Shared::Trips', type: :request do
|
|||
|
||||
it 'enables permanent sharing' do
|
||||
patch trip_path(trip),
|
||||
params: { sharing: { enabled: '1', expiration: 'permanent' } },
|
||||
as: :json
|
||||
params: { sharing: { enabled: '1', expiration: 'permanent' } }
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(response).to redirect_to(trip_path(trip))
|
||||
trip.reload
|
||||
expect(trip.sharing_settings['expiration']).to eq('permanent')
|
||||
expect(trip.sharing_settings['expires_at']).to be_nil
|
||||
|
|
@ -186,16 +177,12 @@ RSpec.describe 'Shared::Trips', type: :request do
|
|||
trip.enable_sharing!(expiration: '24h')
|
||||
end
|
||||
|
||||
it 'disables sharing and returns success' do
|
||||
it 'disables sharing and redirects to trip' do
|
||||
patch trip_path(trip),
|
||||
params: { sharing: { enabled: '0' } },
|
||||
as: :json
|
||||
params: { sharing: { enabled: '0' } }
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
|
||||
json_response = JSON.parse(response.body)
|
||||
expect(json_response['success']).to be(true)
|
||||
expect(json_response['message']).to eq('Sharing disabled successfully')
|
||||
expect(response).to redirect_to(trip_path(trip))
|
||||
expect(flash[:notice]).to eq('Trip was successfully updated.')
|
||||
|
||||
trip.reload
|
||||
expect(trip.sharing_enabled?).to be(false)
|
||||
|
|
@ -205,8 +192,7 @@ RSpec.describe 'Shared::Trips', type: :request do
|
|||
context 'when trip does not exist' do
|
||||
it 'returns not found' do
|
||||
patch trip_path(id: 999999),
|
||||
params: { sharing: { enabled: '1' } },
|
||||
as: :json
|
||||
params: { sharing: { enabled: '1' } }
|
||||
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
|
|
@ -218,8 +204,7 @@ RSpec.describe 'Shared::Trips', type: :request do
|
|||
|
||||
it 'returns not found' do
|
||||
patch trip_path(other_trip),
|
||||
params: { sharing: { enabled: '1' } },
|
||||
as: :json
|
||||
params: { sharing: { enabled: '1' } }
|
||||
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
|
|
@ -229,8 +214,7 @@ RSpec.describe 'Shared::Trips', type: :request do
|
|||
context 'when user is not signed in' do
|
||||
it 'returns unauthorized' do
|
||||
patch trip_path(trip),
|
||||
params: { sharing: { enabled: '1' } },
|
||||
as: :json
|
||||
params: { sharing: { enabled: '1' } }
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
|
|
|
|||
199
spec/support/shared_examples/shareable_examples.rb
Normal file
199
spec/support/shared_examples/shareable_examples.rb
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'shareable' do
|
||||
let(:user) { create(:user) }
|
||||
let(:shareable_model) { described_class.name.underscore.to_sym }
|
||||
let(:shareable) { create(shareable_model, user: user) }
|
||||
|
||||
describe '#generate_sharing_uuid' do
|
||||
it 'generates a UUID before create' do
|
||||
new_record = build(shareable_model, user: user)
|
||||
expect(new_record.sharing_uuid).to be_nil
|
||||
new_record.save!
|
||||
expect(new_record.sharing_uuid).to be_present
|
||||
expect(new_record.sharing_uuid).to match(/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#sharing_enabled?' do
|
||||
it 'returns false by default' do
|
||||
expect(shareable.sharing_enabled?).to be false
|
||||
end
|
||||
|
||||
it 'returns true when enabled' do
|
||||
shareable.update!(sharing_settings: { 'enabled' => true })
|
||||
expect(shareable.sharing_enabled?).to be true
|
||||
end
|
||||
|
||||
it 'returns false when disabled' do
|
||||
shareable.update!(sharing_settings: { 'enabled' => false })
|
||||
expect(shareable.sharing_enabled?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe '#sharing_expired?' do
|
||||
it 'returns false when no expiration is set' do
|
||||
expect(shareable.sharing_expired?).to be false
|
||||
end
|
||||
|
||||
it 'returns false when expires_at is in the future' do
|
||||
shareable.update!(sharing_settings: {
|
||||
'expiration' => '24h',
|
||||
'expires_at' => 1.hour.from_now.iso8601
|
||||
})
|
||||
expect(shareable.sharing_expired?).to be false
|
||||
end
|
||||
|
||||
it 'returns true when expires_at is in the past' do
|
||||
shareable.update!(sharing_settings: {
|
||||
'expiration' => '1h',
|
||||
'expires_at' => 1.hour.ago.iso8601
|
||||
})
|
||||
expect(shareable.sharing_expired?).to be true
|
||||
end
|
||||
|
||||
it 'returns true when expiration is set but expires_at is missing' do
|
||||
shareable.update!(sharing_settings: {
|
||||
'expiration' => '24h',
|
||||
'expires_at' => nil
|
||||
})
|
||||
expect(shareable.sharing_expired?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
describe '#public_accessible?' do
|
||||
it 'returns false by default' do
|
||||
expect(shareable.public_accessible?).to be false
|
||||
end
|
||||
|
||||
it 'returns true when enabled and not expired' do
|
||||
shareable.update!(sharing_settings: {
|
||||
'enabled' => true,
|
||||
'expiration' => '24h',
|
||||
'expires_at' => 1.hour.from_now.iso8601
|
||||
})
|
||||
expect(shareable.public_accessible?).to be true
|
||||
end
|
||||
|
||||
it 'returns false when enabled but expired' do
|
||||
shareable.update!(sharing_settings: {
|
||||
'enabled' => true,
|
||||
'expiration' => '1h',
|
||||
'expires_at' => 1.hour.ago.iso8601
|
||||
})
|
||||
expect(shareable.public_accessible?).to be false
|
||||
end
|
||||
|
||||
it 'returns false when disabled' do
|
||||
shareable.update!(sharing_settings: {
|
||||
'enabled' => false,
|
||||
'expiration' => '24h',
|
||||
'expires_at' => 1.hour.from_now.iso8601
|
||||
})
|
||||
expect(shareable.public_accessible?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe '#enable_sharing!' do
|
||||
it 'enables sharing with default 24h expiration' do
|
||||
shareable.enable_sharing!
|
||||
expect(shareable.sharing_enabled?).to be true
|
||||
expect(shareable.sharing_settings['expiration']).to eq('24h')
|
||||
expect(shareable.sharing_settings['expires_at']).to be_present
|
||||
end
|
||||
|
||||
it 'enables sharing with custom expiration' do
|
||||
shareable.enable_sharing!(expiration: '1h')
|
||||
expect(shareable.sharing_enabled?).to be true
|
||||
expect(shareable.sharing_settings['expiration']).to eq('1h')
|
||||
end
|
||||
|
||||
it 'enables sharing with permanent expiration' do
|
||||
shareable.enable_sharing!(expiration: 'permanent')
|
||||
expect(shareable.sharing_enabled?).to be true
|
||||
expect(shareable.sharing_settings['expiration']).to eq('permanent')
|
||||
expect(shareable.sharing_settings['expires_at']).to be_nil
|
||||
end
|
||||
|
||||
it 'defaults to 24h for invalid expiration' do
|
||||
shareable.enable_sharing!(expiration: 'invalid')
|
||||
expect(shareable.sharing_settings['expiration']).to eq('24h')
|
||||
end
|
||||
|
||||
it 'generates a sharing_uuid if not present' do
|
||||
shareable.update_column(:sharing_uuid, nil)
|
||||
shareable.enable_sharing!
|
||||
expect(shareable.sharing_uuid).to be_present
|
||||
end
|
||||
|
||||
it 'keeps existing sharing_uuid' do
|
||||
original_uuid = shareable.sharing_uuid
|
||||
shareable.enable_sharing!
|
||||
expect(shareable.sharing_uuid).to eq(original_uuid)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#disable_sharing!' do
|
||||
before do
|
||||
shareable.enable_sharing!(expiration: '24h')
|
||||
end
|
||||
|
||||
it 'disables sharing' do
|
||||
shareable.disable_sharing!
|
||||
expect(shareable.sharing_enabled?).to be false
|
||||
end
|
||||
|
||||
it 'clears expiration settings' do
|
||||
shareable.disable_sharing!
|
||||
expect(shareable.sharing_settings['expiration']).to be_nil
|
||||
expect(shareable.sharing_settings['expires_at']).to be_nil
|
||||
end
|
||||
|
||||
it 'keeps the sharing_uuid' do
|
||||
original_uuid = shareable.sharing_uuid
|
||||
shareable.disable_sharing!
|
||||
expect(shareable.sharing_uuid).to eq(original_uuid)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#generate_new_sharing_uuid!' do
|
||||
it 'generates a new UUID' do
|
||||
original_uuid = shareable.sharing_uuid
|
||||
shareable.generate_new_sharing_uuid!
|
||||
expect(shareable.sharing_uuid).not_to eq(original_uuid)
|
||||
expect(shareable.sharing_uuid).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
describe '#share_notes?' do
|
||||
it 'returns false by default' do
|
||||
expect(shareable.share_notes?).to be false
|
||||
end
|
||||
|
||||
it 'returns true when share_notes is enabled' do
|
||||
shareable.update!(sharing_settings: { 'share_notes' => true })
|
||||
expect(shareable.share_notes?).to be true
|
||||
end
|
||||
|
||||
it 'returns false when share_notes is disabled' do
|
||||
shareable.update!(sharing_settings: { 'share_notes' => false })
|
||||
expect(shareable.share_notes?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe '#share_photos?' do
|
||||
it 'returns false by default' do
|
||||
expect(shareable.share_photos?).to be false
|
||||
end
|
||||
|
||||
it 'returns true when share_photos is enabled' do
|
||||
shareable.update!(sharing_settings: { 'share_photos' => true })
|
||||
expect(shareable.share_photos?).to be true
|
||||
end
|
||||
|
||||
it 'returns false when share_photos is disabled' do
|
||||
shareable.update!(sharing_settings: { 'share_photos' => false })
|
||||
expect(shareable.share_photos?).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in a new issue