diff --git a/app/controllers/channel_zones_controller.rb b/app/controllers/channel_zones_controller.rb deleted file mode 100644 index 0f4ee8a..0000000 --- a/app/controllers/channel_zones_controller.rb +++ /dev/null @@ -1,59 +0,0 @@ -class ChannelZonesController < ApplicationController - before_action :set_codeplug - before_action :set_zone - before_action :authorize_codeplug - before_action :set_channel_zone, only: [ :destroy ] - - def create - @channel = @codeplug.channels.find_by(id: channel_zone_params[:channel_id]) - - unless @channel - flash[:alert] = "Channel not found in this codeplug." - redirect_to codeplug_zone_path(@codeplug, @zone), status: :unprocessable_entity - return - end - - # Get the next available position - max_position = @zone.channel_zones.maximum(:position) || 0 - @channel_zone = @zone.channel_zones.new( - channel: @channel, - position: max_position + 1 - ) - - if @channel_zone.save - redirect_to codeplug_zone_path(@codeplug, @zone), notice: "Channel was successfully added to zone." - else - flash[:alert] = @channel_zone.errors.full_messages.join(", ") - redirect_to codeplug_zone_path(@codeplug, @zone), status: :unprocessable_entity - end - end - - def destroy - @channel_zone.destroy! - redirect_to codeplug_zone_path(@codeplug, @zone), notice: "Channel was successfully removed from zone." - end - - private - - def set_codeplug - @codeplug = Codeplug.find(params[:codeplug_id]) - end - - def set_zone - @zone = @codeplug.zones.find(params[:zone_id]) - end - - def set_channel_zone - @channel_zone = @zone.channel_zones.find(params[:id]) - end - - def authorize_codeplug - unless @codeplug.user == current_user - redirect_to codeplugs_path, alert: "You don't have permission to access this codeplug." - end - end - - def channel_zone_params - params.require(:channel_zone).permit(:channel_id) - end -end diff --git a/app/controllers/zones_controller.rb b/app/controllers/zones_controller.rb index 11ec490..f8ef8be 100644 --- a/app/controllers/zones_controller.rb +++ b/app/controllers/zones_controller.rb @@ -1,75 +1,37 @@ class ZonesController < ApplicationController - # Only run these for nested routes (when codeplug_id is present) - before_action :set_codeplug, if: :nested_route? - before_action :authorize_codeplug, if: :nested_route? before_action :set_zone, only: [ :show, :edit, :update, :destroy, :update_positions ] - before_action :authorize_zone, only: [ :show, :edit, :update, :destroy, :update_positions ], unless: :nested_route? + before_action :authorize_zone_view, only: [ :show ] + before_action :authorize_zone_edit, only: [ :edit, :update, :destroy, :update_positions ] def index - if nested_route? - # Nested route: show zones for specific codeplug - @zones = @codeplug.zones.order(:name) - else - # Standalone route: show zones available to current user - @zones = Zone.available_to_user(current_user).order(:name) - end + @zones = Zone.available_to_user(current_user).order(:name) end def show - if nested_route? - # Nested route: Get channels for codeplug - channel_ids_in_zone = @zone.channel_zones.pluck(:channel_id) - - @available_channels = @codeplug.channels - .where.not(id: channel_ids_in_zone) - .order(:long_name) - else - # Standalone route: Show zone systems (placeholder for future functionality) - @zone_systems = @zone.zone_systems.includes(:system).order(:position) - end + @zone_systems = @zone.zone_systems.includes(:system).order(:position) end def new - if nested_route? - @zone = @codeplug.zones.new - else - @zone = Zone.new - end + @zone = Zone.new end def edit - # Edit zone form end def create - if nested_route? - @zone = @codeplug.zones.new(zone_params) - @zone.user = current_user - - if @zone.save - redirect_to codeplug_zone_path(@codeplug, @zone), notice: "Zone was successfully created." - else - render :new, status: :unprocessable_entity - end - else - @zone = Zone.new(zone_params) - @zone.user = current_user + @zone = Zone.new(zone_params) + @zone.user = current_user - if @zone.save - redirect_to zone_path(@zone), notice: "Zone was successfully created." - else - render :new, status: :unprocessable_entity - end + if @zone.save + redirect_to zone_path(@zone), notice: "Zone was successfully created." + else + render :new, status: :unprocessable_entity end end def update if @zone.update(zone_params) - if nested_route? - redirect_to codeplug_zone_path(@codeplug, @zone), notice: "Zone was successfully updated." - else - redirect_to zone_path(@zone), notice: "Zone was successfully updated." - end + redirect_to zone_path(@zone), notice: "Zone was successfully updated." else render :edit, status: :unprocessable_entity end @@ -77,43 +39,23 @@ def update def destroy @zone.destroy! - if nested_route? - redirect_to codeplug_zones_path(@codeplug), notice: "Zone was successfully deleted." - else - redirect_to zones_path, notice: "Zone was successfully deleted." - end + redirect_to zones_path, notice: "Zone was successfully deleted." end def update_positions positions_params = params.permit(positions: [ :id, :position ]) ActiveRecord::Base.transaction do - if nested_route? - # Update channel positions for codeplug zones - # First pass: Set temporary positions to avoid uniqueness conflicts - positions_params[:positions].each_with_index do |position_data, index| - channel_zone = @zone.channel_zones.find(position_data[:id]) - channel_zone.update_column(:position, 1000 + index) - end - - # Second pass: Set actual positions - positions_params[:positions].each do |position_data| - channel_zone = @zone.channel_zones.find(position_data[:id]) - channel_zone.update!(position: position_data[:position]) - end - else - # Update zone_system positions for standalone zones - # First pass: Set temporary positions to avoid uniqueness conflicts - positions_params[:positions].each_with_index do |position_data, index| - zone_system = @zone.zone_systems.find(position_data[:id]) - zone_system.update_column(:position, 1000 + index) - end - - # Second pass: Set actual positions - positions_params[:positions].each do |position_data| - zone_system = @zone.zone_systems.find(position_data[:id]) - zone_system.update!(position: position_data[:position]) - end + # First pass: Set temporary positions to avoid uniqueness conflicts + positions_params[:positions].each_with_index do |position_data, index| + zone_system = @zone.zone_systems.find(position_data[:id]) + zone_system.update_column(:position, 1000 + index) + end + + # Second pass: Set actual positions + positions_params[:positions].each do |position_data| + zone_system = @zone.zone_systems.find(position_data[:id]) + zone_system.update!(position: position_data[:position]) end end @@ -122,43 +64,22 @@ def update_positions private - def set_codeplug - @codeplug = Codeplug.find(params[:codeplug_id]) - end - def set_zone - if nested_route? - @zone = @codeplug.zones.find(params[:id]) - else - @zone = Zone.find(params[:id]) - end + @zone = Zone.find(params[:id]) end - def authorize_codeplug - unless @codeplug.user == current_user - redirect_to codeplugs_path, alert: "You don't have permission to access this codeplug." + def authorize_zone_view + unless @zone.viewable_by?(current_user) + head :forbidden end end - def authorize_zone - # For standalone routes, check if user can view/edit the zone - action = action_name.to_sym - - if [ :show ].include?(action) - unless @zone.viewable_by?(current_user) - head :forbidden - end - elsif [ :edit, :update, :destroy, :update_positions ].include?(action) - unless @zone.editable_by?(current_user) - head :forbidden - end + def authorize_zone_edit + unless @zone.editable_by?(current_user) + head :forbidden end end - def nested_route? - params[:codeplug_id].present? - end - def zone_params params.require(:zone).permit(:name, :long_name, :short_name, :public) end diff --git a/app/models/codeplug.rb b/app/models/codeplug.rb index cbe729f..156d6dd 100644 --- a/app/models/codeplug.rb +++ b/app/models/codeplug.rb @@ -1,9 +1,9 @@ class Codeplug < ApplicationRecord # Associations belongs_to :user - has_many :zones, dependent: :destroy has_many :channels, dependent: :destroy has_many :codeplug_zones, dependent: :destroy + has_many :zones, through: :codeplug_zones # Validations validates :name, presence: true diff --git a/app/models/zone.rb b/app/models/zone.rb index 84235ca..f0fc189 100644 --- a/app/models/zone.rb +++ b/app/models/zone.rb @@ -1,7 +1,6 @@ class Zone < ApplicationRecord # Associations belongs_to :user - belongs_to :codeplug, optional: true has_many :channel_zones, dependent: :destroy has_many :channels, through: :channel_zones has_many :codeplug_zones, dependent: :destroy diff --git a/app/views/codeplugs/show.html.erb b/app/views/codeplugs/show.html.erb index 9cdbae5..f135423 100644 --- a/app/views/codeplugs/show.html.erb +++ b/app/views/codeplugs/show.html.erb @@ -146,38 +146,7 @@ - <%# Legacy Zones Section (codeplug-owned zones) %> -
-
-
-
-
Zones
- <%= link_to "Manage Zones", codeplug_zones_path(@codeplug), class: "btn btn-sm btn-primary" %> -
- - <% if @codeplug.zones.any? %> -

<%= pluralize(@codeplug.zones.count, "zone") %>

-
    - <% @codeplug.zones.limit(5).each do |zone| %> -
  • - <%= zone.long_name || zone.name %> - <%= zone.channels.count %> channels -
  • - <% end %> - <% if @codeplug.zones.count > 5 %> -
  • - + <%= @codeplug.zones.count - 5 %> more zones -
  • - <% end %> -
- <% else %> -

No zones configured yet.

- <% end %> -
-
-
- -
+
diff --git a/app/views/zones/_form.html.erb b/app/views/zones/_form.html.erb index b9a07d2..c127cd0 100644 --- a/app/views/zones/_form.html.erb +++ b/app/views/zones/_form.html.erb @@ -1,4 +1,4 @@ -<%= form_with model: @codeplug ? [ @codeplug, zone ] : zone do |f| %> +<%= form_with model: zone do |f| %> <% if zone.errors.any? %>
<%= pluralize(zone.errors.count, "error") %> prohibited this zone from being saved:
@@ -28,22 +28,16 @@
Abbreviated name (for radios with limited display)
- <% unless @codeplug %> -
-
- <%= f.check_box :public, class: "form-check-input" %> - <%= f.label :public, "Make this zone public", class: "form-check-label" %> -
Public zones can be viewed by all users. Private zones are only visible to you.
-
+
+
+ <%= f.check_box :public, class: "form-check-input" %> + <%= f.label :public, "Make this zone public", class: "form-check-label" %> +
Public zones can be viewed by all users. Private zones are only visible to you.
- <% end %> +
<%= f.submit class: "btn btn-primary" %> - <% if @codeplug %> - <%= link_to "Cancel", zone.persisted? ? codeplug_zone_path(@codeplug, zone) : codeplug_zones_path(@codeplug), class: "btn btn-secondary" %> - <% else %> - <%= link_to "Cancel", zone.persisted? ? zone_path(zone) : zones_path, class: "btn btn-secondary" %> - <% end %> + <%= link_to "Cancel", zone.persisted? ? zone_path(zone) : zones_path, class: "btn btn-secondary" %>
<% end %> diff --git a/app/views/zones/edit.html.erb b/app/views/zones/edit.html.erb index 7ea2708..8c47c93 100644 --- a/app/views/zones/edit.html.erb +++ b/app/views/zones/edit.html.erb @@ -1,11 +1,7 @@

Edit Zone - <%= @zone.name %>

- <% if @codeplug %> - <%= link_to "← Back to Zone", codeplug_zone_path(@codeplug, @zone), class: "btn btn-secondary mb-3" %> - <% else %> - <%= link_to "← Back to Zone", zone_path(@zone), class: "btn btn-secondary mb-3" %> - <% end %> + <%= link_to "← Back to Zone", zone_path(@zone), class: "btn btn-secondary mb-3" %>
diff --git a/app/views/zones/index.html.erb b/app/views/zones/index.html.erb index 4fecf99..f95fa9d 100644 --- a/app/views/zones/index.html.erb +++ b/app/views/zones/index.html.erb @@ -1,17 +1,9 @@

Zones

- <% if @codeplug %> - <%= link_to "New Zone", new_codeplug_zone_path(@codeplug), class: "btn btn-primary" %> - <% else %> - <%= link_to "New Zone", new_zone_path, class: "btn btn-primary" %> - <% end %> + <%= link_to "New Zone", new_zone_path, class: "btn btn-primary" %>
- <% if @codeplug %> - <%= link_to "← Back to Codeplug", codeplug_path(@codeplug), class: "btn btn-secondary mb-3" %> - <% end %> - <% if @zones.any? %>
@@ -20,62 +12,41 @@ - <% unless @codeplug %> - - - - <% else %> - - <% end %> + + + <% @zones.each do |zone| %> - <% if @codeplug %> - - <% else %> - - <% end %> + - <% if @codeplug %> - - <% else %> - - - - <% end %> + + + @@ -85,11 +56,7 @@ <% else %>
- <% if @codeplug %> - No zones found. <%= link_to "Create the first one", new_codeplug_zone_path(@codeplug), class: "alert-link" %>. - <% else %> - No zones found. <%= link_to "Create the first one", new_zone_path, class: "alert-link" %>. - <% end %> + No zones found. <%= link_to "Create the first one", new_zone_path, class: "alert-link" %>.
<% end %> diff --git a/app/views/zones/new.html.erb b/app/views/zones/new.html.erb index 59fae00..d232f3b 100644 --- a/app/views/zones/new.html.erb +++ b/app/views/zones/new.html.erb @@ -1,11 +1,7 @@

New Zone

- <% if @codeplug %> - <%= link_to "← Back to Zones", codeplug_zones_path(@codeplug), class: "btn btn-secondary mb-3" %> - <% else %> - <%= link_to "← Back to Zones", zones_path, class: "btn btn-secondary mb-3" %> - <% end %> + <%= link_to "← Back to Zones", zones_path, class: "btn btn-secondary mb-3" %>
diff --git a/app/views/zones/show.html.erb b/app/views/zones/show.html.erb index c87f386..1967cae 100644 --- a/app/views/zones/show.html.erb +++ b/app/views/zones/show.html.erb @@ -2,13 +2,7 @@

<%= @zone.name %>

- <% if @codeplug %> - <%= link_to "Edit", edit_codeplug_zone_path(@codeplug, @zone), class: "btn btn-secondary me-2" %> - <%= button_to "Delete", codeplug_zone_path(@codeplug, @zone), method: :delete, - class: "btn btn-danger", - form: { class: "d-inline" }, - data: { turbo_confirm: "Are you sure?" } %> - <% elsif @zone.editable_by?(current_user) %> + <% if @zone.editable_by?(current_user) %> <%= link_to "Edit", edit_zone_path(@zone), class: "btn btn-secondary me-2" %> <%= button_to "Delete", zone_path(@zone), method: :delete, class: "btn btn-danger", @@ -18,11 +12,7 @@
- <% if @codeplug %> - <%= link_to "← Back to Zones", codeplug_zones_path(@codeplug), class: "btn btn-secondary mb-3" %> - <% else %> - <%= link_to "← Back to Zones", zones_path, class: "btn btn-secondary mb-3" %> - <% end %> + <%= link_to "← Back to Zones", zones_path, class: "btn btn-secondary mb-3" %>
@@ -40,202 +30,135 @@
- <% unless @codeplug %> -
-
- Owner: <%= @zone.user.email %> -
-
- Visibility: - <% if @zone.public? %> - Public - <% else %> - Private - <% end %> -
+
+
+ Owner: <%= @zone.user.email %>
- <% end %> +
+ Visibility: + <% if @zone.public? %> + Public + <% else %> + Private + <% end %> +
+
- <% if @codeplug %> -
-
-
-
Channels
-
+
+
+
+
Systems
+
- <% if @available_channels.any? %> + <% if @zone.editable_by?(current_user) %> + <% available_systems = System.where.not(id: @zone.zone_systems.pluck(:system_id)).order(:name) %> + <% if available_systems.any? %>
- <%= form_with(model: @zone.channel_zones.new, url: codeplug_zone_channel_zones_path(@codeplug, @zone), method: :post, local: true, class: "row g-3") do |f| %> + <%= form_with(model: @zone.zone_systems.new, url: zone_zone_systems_path(@zone), method: :post, local: true, class: "row g-3") do |f| %>
- <%= f.select :channel_id, - options_from_collection_for_select(@available_channels, :id, :long_name), - { prompt: "Select a channel to add..." }, + <%= f.select :system_id, + options_from_collection_for_select(available_systems, :id, :name), + { prompt: "Select a system to add..." }, { class: "form-select" } %>
- <%= f.submit "Add Channel", class: "btn btn-primary" %> + <%= f.submit "Add System", class: "btn btn-primary" %>
<% end %>
<% end %> + <% end %> - <% if @zone.channel_zones.any? %> -

<%= pluralize(@zone.channel_zones.count, "channel") %>

-
- <% @zone.channel_zones.order(:position).each do |channel_zone| %> -
-
-
+ <% if @zone_systems.any? %> +

<%= pluralize(@zone_systems.count, "system") %>

+
+ <% @zone_systems.each do |zone_system| %> +
+
+
+ <% if @zone.editable_by?(current_user) %> -
- <%= channel_zone.channel.long_name %> - <% if channel_zone.channel.system %> - <%= channel_zone.channel.system.mode.upcase %> + <% end %> +
+ <%= zone_system.system.name %> + <%= zone_system.system.mode.upcase %> +
+ + <%= zone_system.system.rx_frequency %> MHz + <% if zone_system.system.tx_frequency != zone_system.system.rx_frequency %> + / <%= zone_system.system.tx_frequency %> MHz <% end %> -
- <%= channel_zone.position %> - <%= link_to "Edit", edit_codeplug_channel_path(@codeplug, channel_zone.channel), class: "btn btn-sm btn-outline-secondary me-2" %> - <%= button_to "Remove", codeplug_zone_channel_zone_path(@codeplug, @zone, channel_zone), - method: :delete, - class: "btn btn-sm btn-outline-danger", - form: { class: "d-inline" }, - data: { turbo_confirm: "Remove this channel from the zone?" } %> -
-
-
- <% end %> -
- <% else %> -

No channels in this zone yet. <%= "Add channels using the form above." if @available_channels.any? %>

- <% end %> -
-
- <% else %> - <%# Standalone zone view - show systems %> -
-
-
-
Systems
-
- - <% if @zone.editable_by?(current_user) %> - <% available_systems = System.where.not(id: @zone.zone_systems.pluck(:system_id)).order(:name) %> - <% if available_systems.any? %> -
-
- <%= form_with(model: @zone.zone_systems.new, url: zone_zone_systems_path(@zone), method: :post, local: true, class: "row g-3") do |f| %> -
- <%= f.select :system_id, - options_from_collection_for_select(available_systems, :id, :name), - { prompt: "Select a system to add..." }, - { class: "form-select" } %> -
-
- <%= f.submit "Add System", class: "btn btn-primary" %> -
- <% end %> -
-
- <% end %> - <% end %> - - <% if @zone_systems.any? %> -

<%= pluralize(@zone_systems.count, "system") %>

-
- <% @zone_systems.each do |zone_system| %> -
-
-
- <% if @zone.editable_by?(current_user) %> - - - - - - <% end %> -
- <%= zone_system.system.name %> - <%= zone_system.system.mode.upcase %> -
- - <%= zone_system.system.rx_frequency %> MHz - <% if zone_system.system.tx_frequency != zone_system.system.rx_frequency %> - / <%= zone_system.system.tx_frequency %> MHz - <% end %> - - <% if zone_system.system.mode == "dmr" || zone_system.system.mode == "p25" %> -
- Talkgroups: - <% if zone_system.zone_system_talkgroups.any? %> -
- <% zone_system.zone_system_talkgroups.includes(system_talk_group: :talk_group).each do |zstg| %> - - <%= zstg.system_talk_group.talk_group.name %> - (<%= zstg.system_talk_group.talk_group.talkgroup_number %>) - TS<%= zstg.system_talk_group.timeslot %> - <% if @zone.editable_by?(current_user) %> - <%= button_to zone_zone_system_zone_system_talkgroup_path(@zone, zone_system, zstg), - method: :delete, - class: "btn btn-sm p-0 ms-1 text-danger", - form: { class: "d-inline" }, - data: { turbo_confirm: "Remove this talkgroup?" } do %> - - - - <% end %> + + <% if zone_system.system.mode == "dmr" || zone_system.system.mode == "p25" %> +
+ Talkgroups: + <% if zone_system.zone_system_talkgroups.any? %> +
+ <% zone_system.zone_system_talkgroups.includes(system_talk_group: :talk_group).each do |zstg| %> + + <%= zstg.system_talk_group.talk_group.name %> + (<%= zstg.system_talk_group.talk_group.talkgroup_number %>) + TS<%= zstg.system_talk_group.timeslot %> + <% if @zone.editable_by?(current_user) %> + <%= button_to zone_zone_system_zone_system_talkgroup_path(@zone, zone_system, zstg), + method: :delete, + class: "btn btn-sm p-0 ms-1 text-danger", + form: { class: "d-inline" }, + data: { turbo_confirm: "Remove this talkgroup?" } do %> + + + <% end %> - - <% end %> -
- <% else %> - None selected - <% end %> - <% if @zone.editable_by?(current_user) %> - <% available_talkgroups = zone_system.system.system_talk_groups.includes(:talk_group).where.not(id: zone_system.zone_system_talkgroups.pluck(:system_talk_group_id)) %> - <% if available_talkgroups.any? %> -
- <%= form_with(model: zone_system.zone_system_talkgroups.new, url: zone_zone_system_zone_system_talkgroups_path(@zone, zone_system), method: :post, local: true, class: "d-flex gap-2 align-items-center") do |f| %> - <%= f.select :system_talk_group_id, - available_talkgroups.map { |stg| ["#{stg.talk_group.name} (#{stg.talk_group.talkgroup_number}) - TS#{stg.timeslot}", stg.id] }, - { prompt: "Select talkgroup..." }, - { class: "form-select form-select-sm", style: "width: auto;" } %> - <%= f.submit "Add Talkgroup", class: "btn btn-sm btn-outline-primary" %> <% end %> -
+ <% end %> +
+ <% else %> + None selected + <% end %> + <% if @zone.editable_by?(current_user) %> + <% available_talkgroups = zone_system.system.system_talk_groups.includes(:talk_group).where.not(id: zone_system.zone_system_talkgroups.pluck(:system_talk_group_id)) %> + <% if available_talkgroups.any? %> +
+ <%= form_with(model: zone_system.zone_system_talkgroups.new, url: zone_zone_system_zone_system_talkgroups_path(@zone, zone_system), method: :post, local: true, class: "d-flex gap-2 align-items-center") do |f| %> + <%= f.select :system_talk_group_id, + available_talkgroups.map { |stg| ["#{stg.talk_group.name} (#{stg.talk_group.talkgroup_number}) - TS#{stg.timeslot}", stg.id] }, + { prompt: "Select talkgroup..." }, + { class: "form-select form-select-sm", style: "width: auto;" } %> + <%= f.submit "Add Talkgroup", class: "btn btn-sm btn-outline-primary" %> + <% end %> +
<% end %> -
- <% end %> -
- <%= zone_system.position %> - <% if @zone.editable_by?(current_user) %> - <%= button_to "Remove", zone_zone_system_path(@zone, zone_system), - method: :delete, - class: "btn btn-sm btn-outline-danger", - form: { class: "d-inline" }, - data: { turbo_confirm: "Remove this system from the zone?" } %> + <% end %> +
<% end %>
+ <%= zone_system.position %> + <% if @zone.editable_by?(current_user) %> + <%= button_to "Remove", zone_zone_system_path(@zone, zone_system), + method: :delete, + class: "btn btn-sm btn-outline-danger", + form: { class: "d-inline" }, + data: { turbo_confirm: "Remove this system from the zone?" } %> + <% end %>
- <% end %> -
- <% else %> -

No systems in this zone yet. <%= "Add systems using the form above." if @zone.editable_by?(current_user) && System.count > 0 %>

- <% end %> -
+
+ <% end %> +
+ <% else %> +

No systems in this zone yet. <%= "Add systems using the form above." if @zone.editable_by?(current_user) && System.count > 0 %>

+ <% end %>
- <% end %> +
diff --git a/config/routes.rb b/config/routes.rb index 683c5f0..a63f633 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -52,13 +52,6 @@ patch :update_positions end end - # Nested zones routes (kept for backward compatibility, will be deprecated) - resources :zones do - resources :channel_zones, only: [ :create, :destroy ] - member do - patch :update_positions - end - end resources :channels end diff --git a/db/migrate/20251218005215_remove_codeplug_id_from_zones.rb b/db/migrate/20251218005215_remove_codeplug_id_from_zones.rb new file mode 100644 index 0000000..a699846 --- /dev/null +++ b/db/migrate/20251218005215_remove_codeplug_id_from_zones.rb @@ -0,0 +1,12 @@ +class RemoveCodeplugIdFromZones < ActiveRecord::Migration[8.1] + def change + # Remove the foreign key constraint first + remove_foreign_key :zones, :codeplugs + + # Remove the index + remove_index :zones, :codeplug_id + + # Remove the column + remove_column :zones, :codeplug_id, :bigint + end +end diff --git a/db/schema.rb b/db/schema.rb index da3c5c0..7bcba1a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.1].define(version: 2025_12_15_054858) do +ActiveRecord::Schema[8.1].define(version: 2025_12_18_005215) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -227,7 +227,6 @@ end create_table "zones", force: :cascade do |t| - t.bigint "codeplug_id" t.datetime "created_at", null: false t.string "long_name" t.string "name", null: false @@ -235,7 +234,6 @@ t.string "short_name" t.datetime "updated_at", null: false t.bigint "user_id", null: false - t.index ["codeplug_id"], name: "index_zones_on_codeplug_id" t.index ["public"], name: "index_zones_on_public" t.index ["user_id"], name: "index_zones_on_user_id" end @@ -263,6 +261,5 @@ add_foreign_key "zone_system_talk_groups", "zone_systems" add_foreign_key "zone_systems", "systems" add_foreign_key "zone_systems", "zones" - add_foreign_key "zones", "codeplugs" add_foreign_key "zones", "users" end diff --git a/db/seeds.rb b/db/seeds.rb index cc0d224..24165d1 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -222,6 +222,151 @@ end puts "Created #{radio_models_data.count} system radio models" + # Create networks for DMR talkgroups + networks_data = [ + { name: "Brandmeister", network_type: "Digital-DMR", description: "Worldwide DMR network" }, + { name: "TGIF", network_type: "Digital-DMR", description: "The Global Internet of Friends" }, + { name: "P25 Network", network_type: "Digital-P25", description: "Project 25 digital network" } + ] + + networks = networks_data.map do |data| + network = Network.find_or_create_by!(name: data[:name]) do |n| + n.network_type = data[:network_type] + n.description = data[:description] + end + network + end + puts "Created #{networks.count} networks" + + # Create some sample systems (repeaters) + dmr_network = Network.find_by!(name: "Brandmeister") + + # Analog system + analog_detail = AnalogModeDetail.find_or_create_by!(id: AnalogModeDetail.maximum(:id).to_i + 1) + analog_system = System.find_or_create_by!(name: "W1DEV Analog Repeater") do |s| + s.mode = "analog" + s.rx_frequency = 146.82 + s.tx_frequency = 146.22 + s.supports_tx_tone = true + s.tx_tone_value = "127.3" + s.supports_rx_tone = true + s.rx_tone_value = "127.3" + s.mode_detail = analog_detail + end + puts "Created analog system: #{analog_system.name}" + + # DMR system + dmr_detail = DmrModeDetail.find_or_create_by!(color_code: 3) do |d| + d.id = DmrModeDetail.maximum(:id).to_i + 1 + end + dmr_system = System.find_or_create_by!(name: "W1DEV DMR Repeater") do |s| + s.mode = "dmr" + s.rx_frequency = 446.5 + s.tx_frequency = 441.5 + s.mode_detail = dmr_detail + end + # Associate DMR system with network + unless dmr_system.networks.include?(dmr_network) + dmr_system.networks << dmr_network + end + puts "Created DMR system: #{dmr_system.name}" + + # Create talkgroups + talkgroups_data = [ + { name: "Local", talkgroup_number: "3100", network: dmr_network }, + { name: "TAC 310", talkgroup_number: "310", network: dmr_network }, + { name: "North America", talkgroup_number: "3", network: dmr_network } + ] + + talkgroups = talkgroups_data.map do |data| + TalkGroup.find_or_create_by!(network: data[:network], talkgroup_number: data[:talkgroup_number]) do |tg| + tg.name = data[:name] + end + end + puts "Created #{talkgroups.count} talkgroups" + + # Create SystemTalkGroups (talkgroups available on the DMR system) + stg_data = [ + { talk_group: TalkGroup.find_by!(name: "Local"), timeslot: 1 }, + { talk_group: TalkGroup.find_by!(name: "TAC 310"), timeslot: 2 }, + { talk_group: TalkGroup.find_by!(name: "North America"), timeslot: 1 } + ] + + stg_data.each do |data| + SystemTalkGroup.find_or_create_by!( + system: dmr_system, + talk_group: data[:talk_group], + timeslot: data[:timeslot] + ) + end + puts "Created #{stg_data.count} system talkgroups" + + # Create standalone zones (new architecture) + # Zone 1: Local analog repeaters + analog_zone = Zone.find_or_create_by!(user: user, name: "Local Analog") do |z| + z.long_name = "Local Analog Repeaters" + z.short_name = "ANALG" + z.public = false + end + + # Add analog system to zone + unless analog_zone.zone_systems.exists?(system: analog_system) + ZoneSystem.create!(zone: analog_zone, system: analog_system, position: 1) + end + puts "Created zone: #{analog_zone.name} with #{analog_zone.zone_systems.count} system(s)" + + # Zone 2: DMR with talkgroups + dmr_zone = Zone.find_or_create_by!(user: user, name: "DMR Repeaters") do |z| + z.long_name = "DMR Repeater Zone" + z.short_name = "DMR" + z.public = false + end + + # Add DMR system to zone + zone_system = dmr_zone.zone_systems.find_or_create_by!(system: dmr_system) do |zs| + zs.position = 1 + end + + # Add talkgroups to zone_system + stg_data.each do |data| + stg = SystemTalkGroup.find_by!(system: dmr_system, talk_group: data[:talk_group]) + unless zone_system.zone_system_talkgroups.exists?(system_talk_group: stg) + ZoneSystemTalkGroup.create!(zone_system: zone_system, system_talk_group: stg) + end + end + puts "Created zone: #{dmr_zone.name} with #{dmr_zone.zone_systems.count} system(s) and #{zone_system.zone_system_talkgroups.count} talkgroups" + + # Create a public zone for sharing + public_zone = Zone.find_or_create_by!(user: user, name: "Sample Public Zone") do |z| + z.long_name = "Sample Public Zone" + z.short_name = "PUB" + z.public = true + end + puts "Created public zone: #{public_zone.name}" + + # Create a codeplug and add zones to it (new architecture) + codeplug = Codeplug.find_or_create_by!(user: user, name: "My First Codeplug") do |c| + c.description = "A sample codeplug demonstrating the zone architecture" + c.public = false + end + + # Add zones to codeplug via CodeplugZone + [ analog_zone, dmr_zone ].each_with_index do |zone, index| + CodeplugZone.find_or_create_by!(codeplug: codeplug, zone: zone) do |cz| + cz.position = index + 1 + end + end + puts "Created codeplug: #{codeplug.name} with #{codeplug.codeplug_zones.count} zone(s)" + + # Generate channels from zones + if codeplug.channels.empty? + generator = ChannelGenerator.new(codeplug) + result = generator.generate_channels + puts "Generated #{result[:channels_created]} channel(s) from #{result[:zones_processed]} zone(s)" + else + puts "Codeplug already has #{codeplug.channels.count} channel(s)" + end + puts "\n=== Seed data complete ===" puts "Login with: dev@example.com / password123" puts "Run 'rails db:seed' again to update seed data (idempotent)" diff --git a/test/controllers/channel_zones_controller_test.rb b/test/controllers/channel_zones_controller_test.rb deleted file mode 100644 index 3ec3a00..0000000 --- a/test/controllers/channel_zones_controller_test.rb +++ /dev/null @@ -1,143 +0,0 @@ -require "test_helper" - -class ChannelZonesControllerTest < ActionDispatch::IntegrationTest - setup do - @user = create(:user) - @other_user = create(:user) - @codeplug = create(:codeplug, user: @user) - @other_codeplug = create(:codeplug, user: @other_user) - @zone = create(:zone, codeplug: @codeplug, name: "Zone 1") - @other_zone = create(:zone, codeplug: @other_codeplug, name: "Other Zone") - @system = create(:system) - @channel = create(:channel, codeplug: @codeplug, system: @system, long_name: "Channel 1") - @other_channel = create(:channel, codeplug: @other_codeplug, system: @system, long_name: "Other Channel") - end - - # Create Action Tests - test "should add channel to own zone" do - log_in_as(@user) - - assert_difference("ChannelZone.count", 1) do - post codeplug_zone_channel_zones_path(@codeplug, @zone), params: { - channel_zone: { - channel_id: @channel.id - } - } - end - - channel_zone = ChannelZone.last - assert_equal @zone, channel_zone.zone - assert_equal @channel, channel_zone.channel - assert_equal 1, channel_zone.position - assert_redirected_to codeplug_zone_path(@codeplug, @zone) - assert_equal "Channel was successfully added to zone.", flash[:notice] - end - - test "should set correct position when adding channel to zone with existing channels" do - log_in_as(@user) - channel2 = create(:channel, codeplug: @codeplug, system: @system, long_name: "Channel 2") - channel3 = create(:channel, codeplug: @codeplug, system: @system, long_name: "Channel 3") - create(:channel_zone, zone: @zone, channel: @channel, position: 1) - create(:channel_zone, zone: @zone, channel: channel2, position: 2) - - assert_difference("ChannelZone.count", 1) do - post codeplug_zone_channel_zones_path(@codeplug, @zone), params: { - channel_zone: { - channel_id: channel3.id - } - } - end - - channel_zone = ChannelZone.last - assert_equal 3, channel_zone.position - end - - test "should not add channel from other user's codeplug to zone" do - log_in_as(@user) - - assert_no_difference("ChannelZone.count") do - post codeplug_zone_channel_zones_path(@codeplug, @zone), params: { - channel_zone: { - channel_id: @other_channel.id - } - } - end - - assert_response :unprocessable_entity - end - - test "should not add duplicate channel to zone" do - log_in_as(@user) - create(:channel_zone, zone: @zone, channel: @channel, position: 1) - - assert_no_difference("ChannelZone.count") do - post codeplug_zone_channel_zones_path(@codeplug, @zone), params: { - channel_zone: { - channel_id: @channel.id - } - } - end - - assert_response :unprocessable_entity - end - - test "should not add channel to other user's zone" do - log_in_as(@user) - - assert_no_difference("ChannelZone.count") do - post codeplug_zone_channel_zones_path(@other_codeplug, @other_zone), params: { - channel_zone: { - channel_id: @channel.id - } - } - end - - assert_redirected_to codeplugs_path - end - - test "should require login for create" do - assert_no_difference("ChannelZone.count") do - post codeplug_zone_channel_zones_path(@codeplug, @zone), params: { - channel_zone: { - channel_id: @channel.id - } - } - end - - assert_redirected_to login_path - end - - # Destroy Action Tests - test "should remove channel from own zone" do - log_in_as(@user) - channel_zone = create(:channel_zone, zone: @zone, channel: @channel, position: 1) - - assert_difference("ChannelZone.count", -1) do - delete codeplug_zone_channel_zone_path(@codeplug, @zone, channel_zone) - end - - assert_redirected_to codeplug_zone_path(@codeplug, @zone) - assert_equal "Channel was successfully removed from zone.", flash[:notice] - end - - test "should not remove channel from other user's zone" do - log_in_as(@user) - other_channel_zone = create(:channel_zone, zone: @other_zone, channel: @other_channel, position: 1) - - assert_no_difference("ChannelZone.count") do - delete codeplug_zone_channel_zone_path(@other_codeplug, @other_zone, other_channel_zone) - end - - assert_redirected_to codeplugs_path - end - - test "should require login for destroy" do - channel_zone = create(:channel_zone, zone: @zone, channel: @channel, position: 1) - - assert_no_difference("ChannelZone.count") do - delete codeplug_zone_channel_zone_path(@codeplug, @zone, channel_zone) - end - - assert_redirected_to login_path - end -end diff --git a/test/controllers/zones_controller_test.rb b/test/controllers/zones_controller_test.rb index a5b0896..921111b 100644 --- a/test/controllers/zones_controller_test.rb +++ b/test/controllers/zones_controller_test.rb @@ -4,93 +4,108 @@ class ZonesControllerTest < ActionDispatch::IntegrationTest setup do @user = create(:user) @other_user = create(:user) - @codeplug = create(:codeplug, user: @user) - @other_codeplug = create(:codeplug, user: @other_user) - @zone = create(:zone, codeplug: @codeplug, name: "Zone 1") - @other_zone = create(:zone, codeplug: @other_codeplug, name: "Other Zone") + @zone = create(:zone, user: @user, name: "Zone 1") + @other_zone = create(:zone, user: @other_user, name: "Other Zone") end # Index Action Tests - test "should get index for own codeplug" do + test "should show user's zones and public zones" do log_in_as(@user) - get codeplug_zones_path(@codeplug) - assert_response :success - assert_select "h1", "Zones" - end + # Create test data + my_public_zone = create(:zone, user: @user, public: true, name: "My Public Zone") + my_private_zone = create(:zone, user: @user, public: false, name: "My Private Zone") + other_public_zone = create(:zone, user: @other_user, public: true, name: "Other Public Zone") + other_private_zone = create(:zone, user: @other_user, public: false, name: "Other Private Zone") - test "should not get index for other user's codeplug" do - log_in_as(@user) - get codeplug_zones_path(@other_codeplug) + get zones_path - assert_redirected_to codeplugs_path - assert_equal "You don't have permission to access this codeplug.", flash[:alert] + assert_response :success + # Should see own zones (both public and private) + assert_select "body", text: /My Public Zone/ + assert_select "body", text: /My Private Zone/ + # Should see other users' public zones + assert_select "body", text: /Other Public Zone/ + # Should NOT see other users' private zones + assert_select "body", { text: /Other Private Zone/, count: 0 } end test "should require login for index" do - get codeplug_zones_path(@codeplug) + get zones_path assert_redirected_to login_path end # Show Action Tests - test "should show zone for own codeplug" do + test "should display public zone to any user" do log_in_as(@user) - get codeplug_zone_path(@codeplug, @zone) + public_zone = create(:zone, user: @other_user, public: true, name: "Public Zone") + + get zone_path(public_zone) assert_response :success - assert_select "h1", @zone.name + assert_select "h1", "Public Zone" end - test "should not show zone from other user's codeplug" do + test "should display own private zone" do log_in_as(@user) - get codeplug_zone_path(@other_codeplug, @other_zone) + my_private_zone = create(:zone, user: @user, public: false, name: "My Private Zone") - assert_redirected_to codeplugs_path + get zone_path(my_private_zone) + + assert_response :success + assert_select "h1", "My Private Zone" + end + + test "should not display other user's private zone" do + log_in_as(@user) + other_private_zone = create(:zone, user: @other_user, public: false, name: "Private Zone") + + get zone_path(other_private_zone) + + assert_response :forbidden end test "should require login for show" do - get codeplug_zone_path(@codeplug, @zone) + public_zone = create(:zone, user: @user, public: true) + + get zone_path(public_zone) + assert_redirected_to login_path end # New Action Tests - test "should get new for own codeplug" do + test "should get new" do log_in_as(@user) - get new_codeplug_zone_path(@codeplug) + get new_zone_path assert_response :success assert_select "h1", "New Zone" end - test "should not get new for other user's codeplug" do - log_in_as(@user) - get new_codeplug_zone_path(@other_codeplug) - - assert_redirected_to codeplugs_path - end - test "should require login for new" do - get new_codeplug_zone_path(@codeplug) + get new_zone_path assert_redirected_to login_path end # Create Action Tests - test "should create zone for own codeplug" do + test "should create zone for logged in user" do log_in_as(@user) assert_difference("Zone.count", 1) do - post codeplug_zones_path(@codeplug), params: { + post zones_path, params: { zone: { name: "New Zone", long_name: "New Long Zone Name", - short_name: "NZN" + short_name: "NZN", + public: false } } end zone = Zone.last - assert_equal @codeplug, zone.codeplug - assert_redirected_to codeplug_zone_path(@codeplug, zone) + assert_equal @user, zone.user + assert_equal "New Zone", zone.name + assert_redirected_to zone_path(zone) assert_equal "Zone was successfully created.", flash[:notice] end @@ -98,7 +113,7 @@ class ZonesControllerTest < ActionDispatch::IntegrationTest log_in_as(@user) assert_no_difference("Zone.count") do - post codeplug_zones_path(@codeplug), params: { + post zones_path, params: { zone: { name: "", long_name: "Test" @@ -109,23 +124,9 @@ class ZonesControllerTest < ActionDispatch::IntegrationTest assert_response :unprocessable_entity end - test "should not create zone for other user's codeplug" do - log_in_as(@user) - - assert_no_difference("Zone.count") do - post codeplug_zones_path(@other_codeplug), params: { - zone: { - name: "Hacked Zone" - } - } - end - - assert_redirected_to codeplugs_path - end - test "should require login for create" do assert_no_difference("Zone.count") do - post codeplug_zones_path(@codeplug), params: { + post zones_path, params: { zone: { name: "Test" } @@ -138,7 +139,7 @@ class ZonesControllerTest < ActionDispatch::IntegrationTest # Edit Action Tests test "should get edit for own zone" do log_in_as(@user) - get edit_codeplug_zone_path(@codeplug, @zone) + get edit_zone_path(@zone) assert_response :success assert_select "h1", text: /Edit Zone/ @@ -146,13 +147,13 @@ class ZonesControllerTest < ActionDispatch::IntegrationTest test "should not get edit for other user's zone" do log_in_as(@user) - get edit_codeplug_zone_path(@other_codeplug, @other_zone) + get edit_zone_path(@other_zone) - assert_redirected_to codeplugs_path + assert_response :forbidden end test "should require login for edit" do - get edit_codeplug_zone_path(@codeplug, @zone) + get edit_zone_path(@zone) assert_redirected_to login_path end @@ -160,7 +161,7 @@ class ZonesControllerTest < ActionDispatch::IntegrationTest test "should update own zone" do log_in_as(@user) - patch codeplug_zone_path(@codeplug, @zone), params: { + patch zone_path(@zone), params: { zone: { name: "Updated Zone", long_name: "Updated Long Name" @@ -170,14 +171,14 @@ class ZonesControllerTest < ActionDispatch::IntegrationTest @zone.reload assert_equal "Updated Zone", @zone.name assert_equal "Updated Long Name", @zone.long_name - assert_redirected_to codeplug_zone_path(@codeplug, @zone) + assert_redirected_to zone_path(@zone) assert_equal "Zone was successfully updated.", flash[:notice] end test "should not update without name" do log_in_as(@user) - patch codeplug_zone_path(@codeplug, @zone), params: { + patch zone_path(@zone), params: { zone: { name: "" } @@ -190,7 +191,7 @@ class ZonesControllerTest < ActionDispatch::IntegrationTest log_in_as(@user) original_name = @other_zone.name - patch codeplug_zone_path(@other_codeplug, @other_zone), params: { + patch zone_path(@other_zone), params: { zone: { name: "Hacked Name" } @@ -198,11 +199,11 @@ class ZonesControllerTest < ActionDispatch::IntegrationTest @other_zone.reload assert_equal original_name, @other_zone.name - assert_redirected_to codeplugs_path + assert_response :forbidden end test "should require login for update" do - patch codeplug_zone_path(@codeplug, @zone), params: { + patch zone_path(@zone), params: { zone: { name: "New Name" } @@ -216,10 +217,10 @@ class ZonesControllerTest < ActionDispatch::IntegrationTest log_in_as(@user) assert_difference("Zone.count", -1) do - delete codeplug_zone_path(@codeplug, @zone) + delete zone_path(@zone) end - assert_redirected_to codeplug_zones_path(@codeplug) + assert_redirected_to zones_path assert_equal "Zone was successfully deleted.", flash[:notice] end @@ -227,222 +228,67 @@ class ZonesControllerTest < ActionDispatch::IntegrationTest log_in_as(@user) assert_no_difference("Zone.count") do - delete codeplug_zone_path(@other_codeplug, @other_zone) + delete zone_path(@other_zone) end - assert_redirected_to codeplugs_path + assert_response :forbidden end test "should require login for destroy" do assert_no_difference("Zone.count") do - delete codeplug_zone_path(@codeplug, @zone) + delete zone_path(@zone) end assert_redirected_to login_path end - # Update Positions Action Tests - test "should update channel positions for own zone" do + # Update Positions Action Tests (for zone_systems reordering) + test "should update zone_system positions for own zone" do log_in_as(@user) - system = create(:system) - channel1 = create(:channel, codeplug: @codeplug, system: system, long_name: "Channel 1") - channel2 = create(:channel, codeplug: @codeplug, system: system, long_name: "Channel 2") - channel3 = create(:channel, codeplug: @codeplug, system: system, long_name: "Channel 3") + system1 = create(:system, :analog, name: "System 1") + system2 = create(:system, :analog, name: "System 2") + system3 = create(:system, :analog, name: "System 3") - cz1 = create(:channel_zone, zone: @zone, channel: channel1, position: 1) - cz2 = create(:channel_zone, zone: @zone, channel: channel2, position: 2) - cz3 = create(:channel_zone, zone: @zone, channel: channel3, position: 3) + zs1 = create(:zone_system, zone: @zone, system: system1, position: 1) + zs2 = create(:zone_system, zone: @zone, system: system2, position: 2) + zs3 = create(:zone_system, zone: @zone, system: system3, position: 3) - # Reorder: move channel3 to position 1 - patch update_positions_codeplug_zone_path(@codeplug, @zone), params: { + # Reorder: move system3 to position 1 + patch update_positions_zone_path(@zone), params: { positions: [ - { id: cz3.id, position: 1 }, - { id: cz1.id, position: 2 }, - { id: cz2.id, position: 3 } + { id: zs3.id, position: 1 }, + { id: zs1.id, position: 2 }, + { id: zs2.id, position: 3 } ] }, as: :json assert_response :success # Verify positions updated - assert_equal 1, cz3.reload.position - assert_equal 2, cz1.reload.position - assert_equal 3, cz2.reload.position + assert_equal 1, zs3.reload.position + assert_equal 2, zs1.reload.position + assert_equal 3, zs2.reload.position end test "should not update positions for other user's zone" do log_in_as(@user) - system = create(:system) - channel = create(:channel, codeplug: @other_codeplug, system: system) - cz = create(:channel_zone, zone: @other_zone, channel: channel, position: 1) + system = create(:system, :analog) + zs = create(:zone_system, zone: @other_zone, system: system, position: 1) - patch update_positions_codeplug_zone_path(@other_codeplug, @other_zone), params: { + patch update_positions_zone_path(@other_zone), params: { positions: [ - { id: cz.id, position: 2 } + { id: zs.id, position: 2 } ] }, as: :json - assert_redirected_to codeplugs_path + assert_response :forbidden end test "should require login for update_positions" do - patch update_positions_codeplug_zone_path(@codeplug, @zone), params: { + patch update_positions_zone_path(@zone), params: { positions: [] }, as: :json assert_redirected_to login_path end - - # Standalone Zones Routes Tests (new top-level resource) - # These tests are for the new standalone zones functionality (not nested under codeplugs) - - test "standalone index should show user's zones and public zones" do - log_in_as(@user) - - # Create test data - my_public_zone = create(:zone, user: @user, public: true, name: "My Public Zone") - my_private_zone = create(:zone, user: @user, public: false, name: "My Private Zone") - other_public_zone = create(:zone, user: @other_user, public: true, name: "Other Public Zone") - other_private_zone = create(:zone, user: @other_user, public: false, name: "Other Private Zone") - - get zones_path - - assert_response :success - # Should see own zones (both public and private) - assert_select "body", text: /My Public Zone/ - assert_select "body", text: /My Private Zone/ - # Should see other users' public zones - assert_select "body", text: /Other Public Zone/ - # Should NOT see other users' private zones - assert_select "body", { text: /Other Private Zone/, count: 0 } - end - - test "standalone index should require login" do - get zones_path - assert_redirected_to login_path - end - - test "standalone show should display public zone to any user" do - log_in_as(@user) - public_zone = create(:zone, user: @other_user, public: true, name: "Public Zone") - - get zone_path(public_zone) - - assert_response :success - assert_select "h1", "Public Zone" - end - - test "standalone show should display own private zone" do - log_in_as(@user) - my_private_zone = create(:zone, user: @user, public: false, name: "My Private Zone") - - get zone_path(my_private_zone) - - assert_response :success - assert_select "h1", "My Private Zone" - end - - test "standalone show should not display other user's private zone" do - log_in_as(@user) - other_private_zone = create(:zone, user: @other_user, public: false, name: "Private Zone") - - get zone_path(other_private_zone) - - assert_response :forbidden - end - - test "standalone show should require login" do - public_zone = create(:zone, user: @user, public: true) - - get zone_path(public_zone) - - assert_redirected_to login_path - end - - test "standalone new should require login" do - get new_zone_path - assert_redirected_to login_path - end - - test "standalone create should create zone for logged in user" do - log_in_as(@user) - - assert_difference("Zone.count", 1) do - post zones_path, params: { - zone: { - name: "New Standalone Zone", - long_name: "New Standalone Zone Long", - short_name: "NSZ", - public: false - } - } - end - - zone = Zone.last - assert_equal @user, zone.user - assert_equal "New Standalone Zone", zone.name - assert_redirected_to zone_path(zone) - end - - test "standalone create should require login" do - assert_no_difference("Zone.count") do - post zones_path, params: { - zone: { - name: "New Zone" - } - } - end - - assert_redirected_to login_path - end - - test "standalone update should update own zone" do - log_in_as(@user) - my_zone = create(:zone, user: @user, name: "Original Name") - - patch zone_path(my_zone), params: { - zone: { - name: "Updated Name" - } - } - - assert_equal "Updated Name", my_zone.reload.name - assert_redirected_to zone_path(my_zone) - end - - test "standalone update should not update other user's zone" do - log_in_as(@user) - other_zone = create(:zone, user: @other_user, name: "Original Name") - - patch zone_path(other_zone), params: { - zone: { - name: "Hacked Name" - } - } - - assert_equal "Original Name", other_zone.reload.name - assert_response :forbidden - end - - test "standalone destroy should delete own zone" do - log_in_as(@user) - my_zone = create(:zone, user: @user) - - assert_difference("Zone.count", -1) do - delete zone_path(my_zone) - end - - assert_redirected_to zones_path - end - - test "standalone destroy should not delete other user's zone" do - log_in_as(@user) - other_zone = create(:zone, user: @other_user) - - assert_no_difference("Zone.count") do - delete zone_path(other_zone) - end - - assert_response :forbidden - end end diff --git a/test/factories/codeplug_zones.rb b/test/factories/codeplug_zones.rb index 4493850..bbd2225 100644 --- a/test/factories/codeplug_zones.rb +++ b/test/factories/codeplug_zones.rb @@ -3,10 +3,5 @@ association :codeplug association :zone sequence(:position) { |n| n } - - # Ensure zone belongs to the same codeplug - after(:build) do |cz| - cz.zone.codeplug = cz.codeplug if cz.codeplug && cz.zone - end end end diff --git a/test/factories/zones.rb b/test/factories/zones.rb index 9de62a5..40365d9 100644 --- a/test/factories/zones.rb +++ b/test/factories/zones.rb @@ -1,7 +1,6 @@ FactoryBot.define do factory :zone do association :user - association :codeplug name { Faker::Lorem.words(number: 2).join(" ").titleize } long_name { Faker::Lorem.words(number: 4).join(" ").titleize } short_name { Faker::Alphanumeric.alpha(number: 3).upcase } diff --git a/test/models/codeplug_test.rb b/test/models/codeplug_test.rb index b45e83b..302a3b9 100644 --- a/test/models/codeplug_test.rb +++ b/test/models/codeplug_test.rb @@ -54,15 +54,22 @@ class CodeplugTest < ActiveSupport::TestCase assert_equal :belongs_to, association.macro end - test "should have many zones" do + test "should have many zones through codeplug_zones" do codeplug = create(:codeplug) assert_respond_to codeplug, :zones end - test "zones association should be configured with dependent destroy" do + test "zones association should be configured as through" do association = Codeplug.reflect_on_association(:zones) assert_not_nil association, "zones association should exist" assert_equal :has_many, association.macro + assert_equal :codeplug_zones, association.options[:through] + end + + test "should have many codeplug_zones with dependent destroy" do + association = Codeplug.reflect_on_association(:codeplug_zones) + assert_not_nil association, "codeplug_zones association should exist" + assert_equal :has_many, association.macro assert_equal :destroy, association.options[:dependent] end @@ -79,11 +86,17 @@ class CodeplugTest < ActiveSupport::TestCase end # Dependent Destroy Tests - test "destroying codeplug should destroy associated zones" do - codeplug = create(:codeplug) - # Note: Zones will be created in a later issue, so this test will fail until then - # For now, we're just testing the association configuration exists - skip "Zone model not yet implemented" + test "destroying codeplug should destroy associated codeplug_zones" do + user = create(:user) + codeplug = create(:codeplug, user: user) + zone = create(:zone, user: user) + codeplug_zone = create(:codeplug_zone, codeplug: codeplug, zone: zone, position: 1) + + assert_difference("CodeplugZone.count", -1) do + codeplug.destroy! + end + # Zone itself should still exist (it's standalone) + assert Zone.exists?(zone.id) end test "destroying codeplug should destroy associated channels" do diff --git a/test/models/codeplug_zone_test.rb b/test/models/codeplug_zone_test.rb index 9180d10..73698ba 100644 --- a/test/models/codeplug_zone_test.rb +++ b/test/models/codeplug_zone_test.rb @@ -73,8 +73,9 @@ class CodeplugZoneTest < ActiveSupport::TestCase # Uniqueness Tests test "should not save codeplug_zone with duplicate zone in same codeplug" do - codeplug = create(:codeplug) - zone = create(:zone, codeplug: codeplug) + user = create(:user) + codeplug = create(:codeplug, user: user) + zone = create(:zone, user: user) create(:codeplug_zone, codeplug: codeplug, zone: zone, position: 1) duplicate = build(:codeplug_zone, codeplug: codeplug, zone: zone, position: 2) @@ -83,21 +84,22 @@ class CodeplugZoneTest < ActiveSupport::TestCase end test "should save codeplug_zone with same zone in different codeplugs" do - codeplug1 = create(:codeplug) - codeplug2 = create(:codeplug) - zone1 = create(:zone, codeplug: codeplug1) - zone2 = create(:zone, codeplug: codeplug2) + user = create(:user) + codeplug1 = create(:codeplug, user: user) + codeplug2 = create(:codeplug, user: user) + zone = create(:zone, user: user) - cz1 = create(:codeplug_zone, codeplug: codeplug1, zone: zone1, position: 1) - cz2 = build(:codeplug_zone, codeplug: codeplug2, zone: zone2, position: 1) + cz1 = create(:codeplug_zone, codeplug: codeplug1, zone: zone, position: 1) + cz2 = build(:codeplug_zone, codeplug: codeplug2, zone: zone, position: 1) - assert cz2.save, "Failed to save same zone in different codeplug" + assert cz2.save, "Failed to save same zone in different codeplugs" end test "should not save codeplug_zone with duplicate position in same codeplug" do - codeplug = create(:codeplug) - zone1 = create(:zone, codeplug: codeplug, name: "Zone 1") - zone2 = create(:zone, codeplug: codeplug, name: "Zone 2") + user = create(:user) + codeplug = create(:codeplug, user: user) + zone1 = create(:zone, user: user, name: "Zone 1") + zone2 = create(:zone, user: user, name: "Zone 2") create(:codeplug_zone, codeplug: codeplug, zone: zone1, position: 1) duplicate = build(:codeplug_zone, codeplug: codeplug, zone: zone2, position: 1) @@ -106,10 +108,11 @@ class CodeplugZoneTest < ActiveSupport::TestCase end test "should save codeplug_zone with same position in different codeplugs" do - codeplug1 = create(:codeplug) - codeplug2 = create(:codeplug) - zone1 = create(:zone, codeplug: codeplug1) - zone2 = create(:zone, codeplug: codeplug2) + user = create(:user) + codeplug1 = create(:codeplug, user: user) + codeplug2 = create(:codeplug, user: user) + zone1 = create(:zone, user: user) + zone2 = create(:zone, user: user) cz1 = create(:codeplug_zone, codeplug: codeplug1, zone: zone1, position: 1) cz2 = build(:codeplug_zone, codeplug: codeplug2, zone: zone2, position: 1) @@ -125,10 +128,11 @@ class CodeplugZoneTest < ActiveSupport::TestCase # Multiple CodeplugZones per Codeplug test "codeplug can have multiple codeplug_zones at different positions" do - codeplug = create(:codeplug) - zone1 = create(:zone, codeplug: codeplug, name: "Zone 1") - zone2 = create(:zone, codeplug: codeplug, name: "Zone 2") - zone3 = create(:zone, codeplug: codeplug, name: "Zone 3") + user = create(:user) + codeplug = create(:codeplug, user: user) + zone1 = create(:zone, user: user, name: "Zone 1") + zone2 = create(:zone, user: user, name: "Zone 2") + zone3 = create(:zone, user: user, name: "Zone 3") cz1 = create(:codeplug_zone, codeplug: codeplug, zone: zone1, position: 1) cz2 = create(:codeplug_zone, codeplug: codeplug, zone: zone2, position: 2) @@ -142,10 +146,11 @@ class CodeplugZoneTest < ActiveSupport::TestCase # Default Scope - Ordering by Position test "codeplug_zones should be ordered by position by default" do - codeplug = create(:codeplug) - zone1 = create(:zone, codeplug: codeplug, name: "Zone 1") - zone2 = create(:zone, codeplug: codeplug, name: "Zone 2") - zone3 = create(:zone, codeplug: codeplug, name: "Zone 3") + user = create(:user) + codeplug = create(:codeplug, user: user) + zone1 = create(:zone, user: user, name: "Zone 1") + zone2 = create(:zone, user: user, name: "Zone 2") + zone3 = create(:zone, user: user, name: "Zone 3") # Create in non-sequential order cz3 = create(:codeplug_zone, codeplug: codeplug, zone: zone3, position: 3) @@ -158,9 +163,10 @@ class CodeplugZoneTest < ActiveSupport::TestCase # Through Association Tests test "codeplug should have zones through codeplug_zones" do - codeplug = create(:codeplug) - zone1 = create(:zone, codeplug: codeplug, name: "Zone 1") - zone2 = create(:zone, codeplug: codeplug, name: "Zone 2") + user = create(:user) + codeplug = create(:codeplug, user: user) + zone1 = create(:zone, user: user, name: "Zone 1") + zone2 = create(:zone, user: user, name: "Zone 2") create(:codeplug_zone, codeplug: codeplug, zone: zone1, position: 1) create(:codeplug_zone, codeplug: codeplug, zone: zone2, position: 2) @@ -171,9 +177,10 @@ class CodeplugZoneTest < ActiveSupport::TestCase end test "zone should have codeplugs through codeplug_zones" do - zone = create(:zone) - codeplug1 = create(:codeplug) - codeplug2 = create(:codeplug) + user = create(:user) + zone = create(:zone, user: user) + codeplug1 = create(:codeplug, user: user) + codeplug2 = create(:codeplug, user: user) create(:codeplug_zone, zone: zone, codeplug: codeplug1, position: 1) create(:codeplug_zone, zone: zone, codeplug: codeplug2, position: 1) diff --git a/test/models/zone_test.rb b/test/models/zone_test.rb index 0a70f25..ec28955 100644 --- a/test/models/zone_test.rb +++ b/test/models/zone_test.rb @@ -7,11 +7,6 @@ class ZoneTest < ActiveSupport::TestCase assert zone.save, "Failed to save zone with valid attributes" end - test "should save zone without codeplug (codeplug is now optional)" do - zone = build(:zone, codeplug: nil) - assert zone.save, "Failed to save zone without codeplug" - end - test "should not save zone without user" do zone = build(:zone, user: nil) assert_not zone.save, "Saved zone without user" @@ -35,17 +30,6 @@ class ZoneTest < ActiveSupport::TestCase end # Association Tests - test "should belong to codeplug" do - zone = build(:zone) - assert_respond_to zone, :codeplug - end - - test "codeplug association should be configured" do - association = Zone.reflect_on_association(:codeplug) - assert_not_nil association, "codeplug association should exist" - assert_equal :belongs_to, association.macro - end - test "should have many channel_zones" do zone = create(:zone) assert_respond_to zone, :channel_zones @@ -70,12 +54,45 @@ class ZoneTest < ActiveSupport::TestCase assert_equal :channel_zones, association.options[:through] end - # Dependent Destroy Tests - test "destroying zone should destroy associated channel_zones" do + test "should have many codeplug_zones" do zone = create(:zone) - # Note: ChannelZone will be created in a later issue, so this test will fail until then - # For now, we're just testing the association configuration exists - skip "ChannelZone model not yet implemented" + assert_respond_to zone, :codeplug_zones + end + + test "codeplug_zones association should be configured with dependent destroy" do + association = Zone.reflect_on_association(:codeplug_zones) + assert_not_nil association, "codeplug_zones association should exist" + assert_equal :has_many, association.macro + assert_equal :destroy, association.options[:dependent] + end + + test "should have many codeplugs through codeplug_zones" do + zone = create(:zone) + assert_respond_to zone, :codeplugs + end + + test "codeplugs association should be configured as through" do + association = Zone.reflect_on_association(:codeplugs) + assert_not_nil association, "codeplugs association should exist" + assert_equal :has_many, association.macro + assert_equal :codeplug_zones, association.options[:through] + end + + test "should have many zone_systems" do + zone = create(:zone) + assert_respond_to zone, :zone_systems + end + + test "zone_systems association should be configured with dependent destroy" do + association = Zone.reflect_on_association(:zone_systems) + assert_not_nil association, "zone_systems association should exist" + assert_equal :has_many, association.macro + assert_equal :destroy, association.options[:dependent] + end + + test "should have many systems through zone_systems" do + zone = create(:zone) + assert_respond_to zone, :systems end # Attribute Tests @@ -94,27 +111,6 @@ class ZoneTest < ActiveSupport::TestCase assert_equal "RPT", zone.short_name end - test "should allow different zones with same name in different codeplugs" do - codeplug1 = create(:codeplug) - codeplug2 = create(:codeplug) - zone1 = create(:zone, codeplug: codeplug1, name: "Zone A") - zone2 = create(:zone, codeplug: codeplug2, name: "Zone A") - - assert zone1.persisted? - assert zone2.persisted? - end - - # Multiple Zones per Codeplug - test "codeplug can have multiple zones" do - codeplug = create(:codeplug) - zone1 = create(:zone, codeplug: codeplug, name: "Zone 1") - zone2 = create(:zone, codeplug: codeplug, name: "Zone 2") - - assert_equal 2, codeplug.zones.count - assert_includes codeplug.zones, zone1 - assert_includes codeplug.zones, zone2 - end - # Name Length Tests (no validation, just storage) test "should store long names" do long_name = "A" * 100 @@ -270,10 +266,32 @@ class ZoneTest < ActiveSupport::TestCase assert zone2.persisted? end - # Codeplug Optional Tests - test "zone can exist without being associated to a codeplug" do - zone = create(:zone, codeplug: nil) - assert_nil zone.codeplug - assert zone.persisted? + # Codeplug Relationship Tests (through CodeplugZone) + test "zone can be added to multiple codeplugs" do + user = create(:user) + zone = create(:zone, user: user) + codeplug1 = create(:codeplug, user: user) + codeplug2 = create(:codeplug, user: user) + + create(:codeplug_zone, codeplug: codeplug1, zone: zone, position: 1) + create(:codeplug_zone, codeplug: codeplug2, zone: zone, position: 1) + + assert_equal 2, zone.codeplugs.count + assert_includes zone.codeplugs, codeplug1 + assert_includes zone.codeplugs, codeplug2 + end + + test "codeplug can have multiple zones" do + user = create(:user) + codeplug = create(:codeplug, user: user) + zone1 = create(:zone, user: user, name: "Zone 1") + zone2 = create(:zone, user: user, name: "Zone 2") + + create(:codeplug_zone, codeplug: codeplug, zone: zone1, position: 1) + create(:codeplug_zone, codeplug: codeplug, zone: zone2, position: 2) + + assert_equal 2, codeplug.zones.count + assert_includes codeplug.zones, zone1 + assert_includes codeplug.zones, zone2 end end diff --git a/test/system/codeplugs_test.rb b/test/system/codeplugs_test.rb index 05ad1e4..036104a 100644 --- a/test/system/codeplugs_test.rb +++ b/test/system/codeplugs_test.rb @@ -85,9 +85,13 @@ class CodeplugsTest < ApplicationSystemTestCase user = create(:user, email: "test@example.com", password: "password123") codeplug = create(:codeplug, user: user, name: "Test Codeplug") - # Create some zones and channels - zone1 = create(:zone, codeplug: codeplug, long_name: "Zone 1") - zone2 = create(:zone, codeplug: codeplug, long_name: "Zone 2") + # Create standalone zones and add to codeplug via CodeplugZone + zone1 = create(:zone, user: user, name: "Zone 1", long_name: "Zone 1") + zone2 = create(:zone, user: user, name: "Zone 2", long_name: "Zone 2") + create(:codeplug_zone, codeplug: codeplug, zone: zone1, position: 1) + create(:codeplug_zone, codeplug: codeplug, zone: zone2, position: 2) + + # Create channels system = create(:system, mode: "dmr") channel1 = create(:channel, codeplug: codeplug, system: system, long_name: "Channel 1") channel2 = create(:channel, codeplug: codeplug, system: system, long_name: "Channel 2") diff --git a/test/system/zones_test.rb b/test/system/zones_test.rb index 5b10d26..5dd41c7 100644 --- a/test/system/zones_test.rb +++ b/test/system/zones_test.rb @@ -1,227 +1,7 @@ require "application_system_test_case" class ZonesTest < ApplicationSystemTestCase - test "creating a new zone" do - user = create(:user, email: "test@example.com", password: "password123") - codeplug = create(:codeplug, user: user, name: "Test Codeplug") - - visit codeplug_path(codeplug) - fill_in "Email", with: "test@example.com" - fill_in "Password", with: "password123" - click_button "Log In" - - click_link "Manage Zones" - click_link "New Zone" - - fill_in "Name", with: "Zone 1" - fill_in "Long name", with: "Local Repeaters" - fill_in "Short name", with: "LCL" - click_button "Create Zone" - - assert_text "Zone was successfully created" - assert_text "Zone 1" - assert_text "Local Repeaters" - assert_text "LCL" - end - - test "editing a zone" do - user = create(:user, email: "test@example.com", password: "password123") - codeplug = create(:codeplug, user: user, name: "Test Codeplug") - zone = create(:zone, codeplug: codeplug, name: "Original Zone") - - visit codeplug_zones_path(codeplug) - fill_in "Email", with: "test@example.com" - fill_in "Password", with: "password123" - click_button "Log In" - - click_link "Edit", match: :first - - fill_in "Name", with: "Updated Zone" - fill_in "Long name", with: "Updated Long Name" - click_button "Update Zone" - - assert_text "Zone was successfully updated" - assert_text "Updated Zone" - assert_text "Updated Long Name" - end - - test "viewing zones index" do - user = create(:user, email: "test@example.com", password: "password123") - codeplug = create(:codeplug, user: user, name: "Test Codeplug") - zone1 = create(:zone, codeplug: codeplug, name: "Zone 1", long_name: "Zone One") - zone2 = create(:zone, codeplug: codeplug, name: "Zone 2", long_name: "Zone Two") - - visit codeplug_zones_path(codeplug) - fill_in "Email", with: "test@example.com" - fill_in "Password", with: "password123" - click_button "Log In" - - assert_text "Zone 1" - assert_text "Zone 2" - assert_text "Zone One" - assert_text "Zone Two" - end - - test "viewing zone details" do - user = create(:user, email: "test@example.com", password: "password123") - codeplug = create(:codeplug, user: user, name: "Test Codeplug") - zone = create(:zone, codeplug: codeplug, name: "Test Zone", long_name: "Test Long Name") - - visit codeplug_zone_path(codeplug, zone) - fill_in "Email", with: "test@example.com" - fill_in "Password", with: "password123" - click_button "Log In" - - assert_text "Test Zone" - assert_text "Test Long Name" - end - - test "empty state shows helpful message" do - user = create(:user, email: "test@example.com", password: "password123") - codeplug = create(:codeplug, user: user, name: "Test Codeplug") - - visit codeplug_zones_path(codeplug) - fill_in "Email", with: "test@example.com" - fill_in "Password", with: "password123" - click_button "Log In" - - assert_text "No zones found" - assert_link "Create the first one" - end - - test "manage zones link from codeplug show page" do - user = create(:user, email: "test@example.com", password: "password123") - codeplug = create(:codeplug, user: user, name: "Test Codeplug") - - visit codeplug_path(codeplug) - fill_in "Email", with: "test@example.com" - fill_in "Password", with: "password123" - click_button "Log In" - - click_link "Manage Zones" - - assert_current_path codeplug_zones_path(codeplug) - assert_text "Zones" - end - - test "cannot access other user's zones" do - user = create(:user, email: "test@example.com", password: "password123") - other_user = create(:user) - other_codeplug = create(:codeplug, user: other_user, name: "Other Codeplug") - - visit codeplug_zones_path(other_codeplug) - fill_in "Email", with: "test@example.com" - fill_in "Password", with: "password123" - click_button "Log In" - - assert_text "You don't have permission to access this codeplug" - assert_current_path codeplugs_path - end - - test "zone shows channels with drag handles" do - user = create(:user, email: "test@example.com", password: "password123") - codeplug = create(:codeplug, user: user, name: "Test Codeplug") - zone = create(:zone, codeplug: codeplug, name: "Test Zone") - system = create(:system) - channel1 = create(:channel, codeplug: codeplug, system: system, long_name: "Channel 1") - channel2 = create(:channel, codeplug: codeplug, system: system, long_name: "Channel 2") - create(:channel_zone, zone: zone, channel: channel1, position: 1) - create(:channel_zone, zone: zone, channel: channel2, position: 2) - - visit codeplug_zone_path(codeplug, zone) - fill_in "Email", with: "test@example.com" - fill_in "Password", with: "password123" - click_button "Log In" - - assert_text "Channel 1" - assert_text "Channel 2" - assert_text "2 channels" - - # Verify drag handles are present (via Bootstrap icon SVG) - assert_selector ".drag-handle", count: 2 - assert_selector ".list-group-item[data-id]", count: 2 - end - - test "adding a channel to a zone" do - user = create(:user, email: "test@example.com", password: "password123") - codeplug = create(:codeplug, user: user, name: "Test Codeplug") - zone = create(:zone, codeplug: codeplug, name: "Test Zone") - system = create(:system) - channel = create(:channel, codeplug: codeplug, system: system, long_name: "Test Channel") - - visit codeplug_zone_path(codeplug, zone) - fill_in "Email", with: "test@example.com" - fill_in "Password", with: "password123" - click_button "Log In" - - # Verify the add channel form is present - assert_selector "select#channel_zone_channel_id" - assert_button "Add Channel" - - select "Test Channel", from: "channel_zone_channel_id" - click_button "Add Channel" - - assert_text "Channel was successfully added to zone" - assert_text "Test Channel" - assert_text "1 channel" - end - - # Skipping due to Turbo confirm dialog issues with Capybara - # Controller tests cover the functionality - test "removing a channel from a zone" do - skip "Turbo confirm dialogs don't work reliably in system tests" - - user = create(:user, email: "test@example.com", password: "password123") - codeplug = create(:codeplug, user: user, name: "Test Codeplug") - zone = create(:zone, codeplug: codeplug, name: "Test Zone") - system = create(:system) - channel = create(:channel, codeplug: codeplug, system: system, long_name: "Test Channel") - create(:channel_zone, zone: zone, channel: channel, position: 1) - - visit codeplug_zone_path(codeplug, zone) - fill_in "Email", with: "test@example.com" - fill_in "Password", with: "password123" - click_button "Log In" - - assert_text "Test Channel" - assert_text "1 channel" - - click_button "Remove" - - assert_text "Channel was successfully removed from zone" - assert_text "No channels in this zone yet" - end - - test "editing a channel from zone view" do - user = create(:user, email: "test@example.com", password: "password123") - codeplug = create(:codeplug, user: user, name: "Test Codeplug") - zone = create(:zone, codeplug: codeplug, name: "Test Zone") - system = create(:system) - channel = create(:channel, codeplug: codeplug, system: system, long_name: "Original Name") - create(:channel_zone, zone: zone, channel: channel, position: 1) - - visit codeplug_zone_path(codeplug, zone) - fill_in "Email", with: "test@example.com" - fill_in "Password", with: "password123" - click_button "Log In" - - # Click the Edit button within the channel list (not the zone edit button) - within(".list-group") do - click_link "Edit" - end - - assert_text "Edit Channel" - fill_in "Long name", with: "Updated Name" - click_button "Update Channel" - - assert_text "Channel was successfully updated" - end - - # ======================================== - # Standalone Zones Tests (top-level resource) - # ======================================== - - test "user can navigate to standalone zones index from navbar" do + test "user can navigate to zones index from navbar" do user = create(:user, email: "test@example.com", password: "password123") create(:zone, user: user, name: "My Zone", public: false) @@ -237,7 +17,7 @@ class ZonesTest < ApplicationSystemTestCase assert_text "My Zone" end - test "standalone zones index shows user's zones and public zones" do + test "zones index shows user's zones and public zones" do user = create(:user, email: "test@example.com", password: "password123") other_user = create(:user, email: "other@example.com") @@ -263,7 +43,7 @@ class ZonesTest < ApplicationSystemTestCase assert_no_text "Other Private Zone" end - test "standalone zones index shows owner and visibility badges" do + test "zones index shows owner and visibility badges" do user = create(:user, email: "test@example.com", password: "password123") other_user = create(:user, email: "other@example.com") @@ -275,7 +55,7 @@ class ZonesTest < ApplicationSystemTestCase fill_in "Password", with: "password123" click_button "Log In" - # Verify the table headers for standalone zones + # Verify the table headers assert_selector "th", text: "Owner" assert_selector "th", text: "Visibility" assert_selector "th", text: "Systems" @@ -287,7 +67,7 @@ class ZonesTest < ApplicationSystemTestCase assert_text "other@example.com" # Owner column for other user's zone end - test "user can view standalone zone details" do + test "user can view zone details" do user = create(:user, email: "test@example.com", password: "password123") zone = create(:zone, user: user, name: "Test Zone", long_name: "Test Long Name", public: false) @@ -336,7 +116,7 @@ class ZonesTest < ApplicationSystemTestCase assert_no_text "Other Private Zone" end - test "standalone zone shows systems with drag-drop reordering" do + test "zone shows systems with drag-drop reordering" do user = create(:user, email: "test@example.com", password: "password123") zone = create(:zone, user: user, name: "Test Zone") system1 = create(:system, name: "System 1", rx_frequency: 145.0, tx_frequency: 145.6) @@ -360,7 +140,7 @@ class ZonesTest < ApplicationSystemTestCase assert_selector ".list-group-item[data-id]", count: 2 end - test "adding a system to standalone zone" do + test "adding a system to zone" do user = create(:user, email: "test@example.com", password: "password123") zone = create(:zone, user: user, name: "Test Zone") system = create(:system, name: "Test System", rx_frequency: 145.0, tx_frequency: 145.6) @@ -382,7 +162,7 @@ class ZonesTest < ApplicationSystemTestCase assert_text "1 system" end - test "standalone zone empty state shows helpful message" do + test "zone empty state shows helpful message" do user = create(:user, email: "test@example.com", password: "password123") zone = create(:zone, user: user, name: "Empty Zone") @@ -394,7 +174,7 @@ class ZonesTest < ApplicationSystemTestCase assert_text "No systems in this zone yet" end - test "creating a new standalone zone" do + test "creating a new zone" do user = create(:user, email: "test@example.com", password: "password123") visit new_zone_path @@ -402,17 +182,17 @@ class ZonesTest < ApplicationSystemTestCase fill_in "Password", with: "password123" click_button "Log In" - fill_in "Name", with: "New Standalone Zone" + fill_in "Name", with: "New Zone" fill_in "Long name", with: "My New Zone Long Name" - fill_in "Short name", with: "NSZ" + fill_in "Short name", with: "NZ" click_button "Create Zone" assert_text "Zone was successfully created" - assert_text "New Standalone Zone" + assert_text "New Zone" assert_text "My New Zone Long Name" end - test "editing standalone zone" do + test "editing zone" do user = create(:user, email: "test@example.com", password: "password123") zone = create(:zone, user: user, name: "Original Name") @@ -430,6 +210,18 @@ class ZonesTest < ApplicationSystemTestCase assert_text "Updated Long Name" end + test "zones index empty state shows helpful message" do + user = create(:user, email: "test@example.com", password: "password123") + + visit zones_path + fill_in "Email", with: "test@example.com" + fill_in "Password", with: "password123" + click_button "Log In" + + assert_text "No zones found" + assert_link "Create the first one" + end + # Talkgroup selection tests for digital systems test "digital system in zone shows talkgroup management" do user = create(:user, email: "test@example.com", password: "password123")
Name Long Name Short NameOwnerVisibilitySystemsChannelsOwnerVisibilitySystems Actions
<%= link_to zone.name, codeplug_zone_path(@codeplug, zone) %><%= link_to zone.name, zone_path(zone) %><%= link_to zone.name, zone_path(zone) %> <%= zone.long_name || "—" %> <%= zone.short_name || "—" %><%= zone.channels.count %> - <% if zone.user == current_user %> - You - <% else %> - <%= zone.user.email %> - <% end %> - - <% if zone.public? %> - Public - <% else %> - Private - <% end %> - <%= zone.zone_systems.count %> + <% if zone.user == current_user %> + You + <% else %> + <%= zone.user.email %> + <% end %> + + <% if zone.public? %> + Public + <% else %> + Private + <% end %> + <%= zone.zone_systems.count %> - <% if @codeplug %> - <%= link_to "View", codeplug_zone_path(@codeplug, zone), class: "btn btn-sm btn-info me-1" %> - <%= link_to "Edit", edit_codeplug_zone_path(@codeplug, zone), class: "btn btn-sm btn-secondary me-1" %> - <%= button_to "Delete", codeplug_zone_path(@codeplug, zone), method: :delete, + <%= link_to "View", zone_path(zone), class: "btn btn-sm btn-info me-1" %> + <% if zone.editable_by?(current_user) %> + <%= link_to "Edit", edit_zone_path(zone), class: "btn btn-sm btn-secondary me-1" %> + <%= button_to "Delete", zone_path(zone), method: :delete, class: "btn btn-sm btn-danger", form: { class: "d-inline" }, data: { turbo_confirm: "Are you sure?" } %> - <% else %> - <%= link_to "View", zone_path(zone), class: "btn btn-sm btn-info me-1" %> - <% if zone.editable_by?(current_user) %> - <%= link_to "Edit", edit_zone_path(zone), class: "btn btn-sm btn-secondary me-1" %> - <%= button_to "Delete", zone_path(zone), method: :delete, - class: "btn btn-sm btn-danger", - form: { class: "d-inline" }, - data: { turbo_confirm: "Are you sure?" } %> - <% end %> <% end %>