forked from fixin.me/fixin.me
Compare commits
1 Commits
pr69-multi
...
fix-sqlite
| Author | SHA1 | Date | |
|---|---|---|---|
| 5e1699a4b5 |
@@ -1,74 +0,0 @@
|
|||||||
name: Tests
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ master ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ master ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test-sqlite:
|
|
||||||
name: Tests (SQLite)
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- uses: ruby/setup-ruby@v1
|
|
||||||
with:
|
|
||||||
ruby-version: '3.3'
|
|
||||||
bundler-cache: true
|
|
||||||
env:
|
|
||||||
BUNDLE_WITH: "sqlite:development:test"
|
|
||||||
|
|
||||||
- name: Set up test database
|
|
||||||
run: bin/rails db:create db:schema:load
|
|
||||||
env:
|
|
||||||
RAILS_ENV: test
|
|
||||||
|
|
||||||
- name: Run tests
|
|
||||||
run: bin/rails test
|
|
||||||
env:
|
|
||||||
RAILS_ENV: test
|
|
||||||
CI: "true"
|
|
||||||
|
|
||||||
test-mysql:
|
|
||||||
name: Tests (MySQL)
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
services:
|
|
||||||
mysql:
|
|
||||||
image: mysql:8.0
|
|
||||||
env:
|
|
||||||
MYSQL_ROOT_PASSWORD: ""
|
|
||||||
MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
|
|
||||||
MYSQL_DATABASE: fixin_test
|
|
||||||
ports:
|
|
||||||
- 3306:3306
|
|
||||||
options: >-
|
|
||||||
--health-cmd="mysqladmin ping"
|
|
||||||
--health-interval=10s
|
|
||||||
--health-timeout=5s
|
|
||||||
--health-retries=3
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- uses: ruby/setup-ruby@v1
|
|
||||||
with:
|
|
||||||
ruby-version: '3.3'
|
|
||||||
bundler-cache: true
|
|
||||||
env:
|
|
||||||
BUNDLE_WITH: "mysql:development:test"
|
|
||||||
|
|
||||||
- name: Set up test database
|
|
||||||
run: bin/rails db:schema:load
|
|
||||||
env:
|
|
||||||
RAILS_ENV: test
|
|
||||||
DB_ADAPTER: mysql
|
|
||||||
|
|
||||||
- name: Run tests
|
|
||||||
run: bin/rails test
|
|
||||||
env:
|
|
||||||
RAILS_ENV: test
|
|
||||||
CI: "true"
|
|
||||||
DB_ADAPTER: mysql
|
|
||||||
@@ -16,7 +16,7 @@ class Quantity < ApplicationRecord
|
|||||||
end
|
end
|
||||||
validates :name, presence: true, uniqueness: {scope: [:user_id, :parent_id]},
|
validates :name, presence: true, uniqueness: {scope: [:user_id, :parent_id]},
|
||||||
length: {maximum: type_for_attribute(:name).limit}
|
length: {maximum: type_for_attribute(:name).limit}
|
||||||
validates :description, length: {maximum: type_for_attribute(:description).limit}
|
validates :description, length: {maximum: type_for_attribute(:description).limit} if type_for_attribute(:description).limit
|
||||||
|
|
||||||
# Update :depths of progenies after parent change
|
# Update :depths of progenies after parent change
|
||||||
before_save if: :parent_changed? do
|
before_save if: :parent_changed? do
|
||||||
@@ -61,18 +61,22 @@ class Quantity < ApplicationRecord
|
|||||||
|
|
||||||
# Return: ordered [sub]hierarchy
|
# Return: ordered [sub]hierarchy
|
||||||
scope :ordered, ->(root: nil, include_root: true) {
|
scope :ordered, ->(root: nil, include_root: true) {
|
||||||
numbered = Arel::Table.new('numbered')
|
q_ordered = Arel::Table.new('q_ordered')
|
||||||
|
cast_type = connection.adapter_name == 'Mysql2' ? 'BINARY' : 'BLOB'
|
||||||
|
|
||||||
self.model.with(numbered: numbered(:parent_id, :name)).with_recursive(arel_table.name => [
|
self.model.with(numbered: numbered(:parent_id, :name)).with_recursive(q_ordered: [
|
||||||
numbered.project(
|
Quantity.from(Arel::Table.new('numbered').as(arel_table.name))
|
||||||
numbered[Arel.star],
|
.select(
|
||||||
numbered.cast(numbered[:child_number], 'BINARY').as('path')
|
arel_table[Arel.star],
|
||||||
).where(numbered[root && include_root ? :id : :parent_id].eq(root)),
|
arel_table.cast(arel_table[:child_number], cast_type).as('path')
|
||||||
numbered.project(
|
).where(arel_table[root && include_root ? :id : :parent_id].eq(root)),
|
||||||
numbered[Arel.star],
|
Quantity.from(Arel::Table.new('numbered').as(arel_table.name))
|
||||||
arel_table[:path].concat(numbered[:child_number])
|
.joins("INNER JOIN q_ordered ON quantities.parent_id = q_ordered.id")
|
||||||
).join(arel_table).on(numbered[:parent_id].eq(arel_table[:id]))
|
.select(
|
||||||
]).order(arel_table[:path])
|
arel_table[Arel.star],
|
||||||
|
q_ordered[:path].concat(arel_table[:child_number])
|
||||||
|
)
|
||||||
|
]).from(q_ordered.as(arel_table.name)).order(arel_table[:path])
|
||||||
}
|
}
|
||||||
|
|
||||||
# TODO: extract named functions to custom Arel extension
|
# TODO: extract named functions to custom Arel extension
|
||||||
@@ -80,20 +84,24 @@ class Quantity < ApplicationRecord
|
|||||||
# be merged with :ordered
|
# be merged with :ordered
|
||||||
# https://gist.github.com/ProGM/c6df08da14708dcc28b5ca325df37ceb#extending-arel
|
# https://gist.github.com/ProGM/c6df08da14708dcc28b5ca325df37ceb#extending-arel
|
||||||
scope :numbered, ->(parent_column, order_column) {
|
scope :numbered, ->(parent_column, order_column) {
|
||||||
select(
|
row_number = Arel::Nodes::NamedFunction.new('ROW_NUMBER', [])
|
||||||
arel_table[Arel.star],
|
.over(Arel::Nodes::Window.new.partition(parent_column).order(order_column))
|
||||||
Arel::Nodes::NamedFunction.new(
|
width = Arel::SelectManager.new.project(
|
||||||
'LPAD',
|
Arel::Nodes::NamedFunction.new('LENGTH', [Arel.star.count])
|
||||||
[
|
).from(arel_table)
|
||||||
Arel::Nodes::NamedFunction.new('ROW_NUMBER', [])
|
|
||||||
.over(Arel::Nodes::Window.new.partition(parent_column).order(order_column)),
|
pad_expr = if connection.adapter_name == 'Mysql2'
|
||||||
Arel::SelectManager.new.project(
|
Arel::Nodes::NamedFunction.new('LPAD', [row_number, width, Arel::Nodes.build_quoted('0')])
|
||||||
Arel::Nodes::NamedFunction.new('LENGTH', [Arel.star.count])
|
else
|
||||||
).from(arel_table),
|
# SQLite: printf('%0' || width || 'd', row_number)
|
||||||
Arel::Nodes.build_quoted('0')
|
fmt = Arel::Nodes::InfixOperation.new('||',
|
||||||
],
|
Arel::Nodes::InfixOperation.new('||', Arel::Nodes.build_quoted('%0'), width),
|
||||||
).as('child_number')
|
Arel::Nodes.build_quoted('d')
|
||||||
)
|
)
|
||||||
|
Arel::Nodes::NamedFunction.new('printf', [fmt, row_number])
|
||||||
|
end
|
||||||
|
|
||||||
|
select(arel_table[Arel.star], pad_expr.as('child_number'))
|
||||||
}
|
}
|
||||||
|
|
||||||
def to_s
|
def to_s
|
||||||
@@ -102,7 +110,7 @@ class Quantity < ApplicationRecord
|
|||||||
|
|
||||||
def to_s_with_depth
|
def to_s_with_depth
|
||||||
# em space, U+2003
|
# em space, U+2003
|
||||||
' ' * depth + name
|
' ' * depth + name
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroyable?
|
def destroyable?
|
||||||
@@ -115,16 +123,18 @@ class Quantity < ApplicationRecord
|
|||||||
|
|
||||||
# Common ancestors, assuming node is a descendant of itself
|
# Common ancestors, assuming node is a descendant of itself
|
||||||
scope :common_ancestors, ->(of) {
|
scope :common_ancestors, ->(of) {
|
||||||
selected = Arel::Table.new('selected')
|
q_common = Arel::Table.new('q_common')
|
||||||
|
|
||||||
# Take unique IDs, so self can be called with parent nodes of collection to
|
# Take unique IDs, so self can be called with parent nodes of collection to
|
||||||
# get common ancestors of collection _excluding_ nodes in collection.
|
# get common ancestors of collection _excluding_ nodes in collection.
|
||||||
uniq_of = of.uniq
|
uniq_of = of.uniq
|
||||||
model.with(selected: self).with_recursive(arel_table.name => [
|
model.with(selected: self).with_recursive(q_common: [
|
||||||
selected.project(selected[Arel.star]).where(selected[:id].in(uniq_of)),
|
Quantity.from(Arel::Table.new('selected').as(arel_table.name))
|
||||||
selected.project(selected[Arel.star])
|
.where(arel_table[:id].in(uniq_of)),
|
||||||
.join(arel_table).on(selected[:id].eq(arel_table[:parent_id]))
|
Quantity.from(Arel::Table.new('selected').as(arel_table.name))
|
||||||
|
.joins("INNER JOIN q_common ON selected.id = q_common.parent_id")
|
||||||
]).select(arel_table[Arel.star])
|
]).select(arel_table[Arel.star])
|
||||||
|
.from(q_common.as(arel_table.name))
|
||||||
.group(column_names)
|
.group(column_names)
|
||||||
.having(arel_table[:id].count.eq(uniq_of.size))
|
.having(arel_table[:id].count.eq(uniq_of.size))
|
||||||
.order(arel_table[:depth].desc)
|
.order(arel_table[:depth].desc)
|
||||||
@@ -132,13 +142,9 @@ class Quantity < ApplicationRecord
|
|||||||
|
|
||||||
# Return: successive record in order of appearance; used for partial view reload
|
# Return: successive record in order of appearance; used for partial view reload
|
||||||
def successive
|
def successive
|
||||||
quantities = Quantity.arel_table
|
ordered = user.quantities.ordered.to_a
|
||||||
Quantity.with(
|
idx = ordered.index { |q| q.id == id }
|
||||||
quantities: user.quantities.ordered.select(
|
ordered[idx + 1] if idx
|
||||||
quantities[Arel.star],
|
|
||||||
Arel::Nodes::NamedFunction.new('LAG', [quantities[:id]]).over.as('lag_id')
|
|
||||||
)
|
|
||||||
).where(quantities[:lag_id].eq(id)).first
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def with_progenies
|
def with_progenies
|
||||||
@@ -151,13 +157,14 @@ class Quantity < ApplicationRecord
|
|||||||
|
|
||||||
# Return: record with ID `of` with its ancestors, sorted by :depth
|
# Return: record with ID `of` with its ancestors, sorted by :depth
|
||||||
scope :with_ancestors, ->(of) {
|
scope :with_ancestors, ->(of) {
|
||||||
selected = Arel::Table.new('selected')
|
q_ancestors = Arel::Table.new('q_ancestors')
|
||||||
|
|
||||||
model.with(selected: self).with_recursive(arel_table.name => [
|
model.with(selected: self).with_recursive(q_ancestors: [
|
||||||
selected.project(selected[Arel.star]).where(selected[:id].eq(of)),
|
Quantity.from(Arel::Table.new('selected').as(arel_table.name))
|
||||||
selected.project(selected[Arel.star])
|
.where(arel_table[:id].eq(of)),
|
||||||
.join(arel_table).on(selected[:id].eq(arel_table[:parent_id]))
|
Quantity.from(Arel::Table.new('selected').as(arel_table.name))
|
||||||
])
|
.joins("INNER JOIN q_ancestors ON selected.id = q_ancestors.parent_id")
|
||||||
|
]).from(q_ancestors.as(arel_table.name))
|
||||||
}
|
}
|
||||||
|
|
||||||
# Return: ancestors of (possibly destroyed) self
|
# Return: ancestors of (possibly destroyed) self
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class Unit < ApplicationRecord
|
|||||||
end
|
end
|
||||||
validates :symbol, presence: true, uniqueness: {scope: :user_id},
|
validates :symbol, presence: true, uniqueness: {scope: :user_id},
|
||||||
length: {maximum: type_for_attribute(:symbol).limit}
|
length: {maximum: type_for_attribute(:symbol).limit}
|
||||||
validates :description, length: {maximum: type_for_attribute(:description).limit}
|
validates :description, length: {maximum: type_for_attribute(:description).limit} if type_for_attribute(:description).limit
|
||||||
validates :multiplier, numericality: {equal_to: 1}, unless: :base
|
validates :multiplier, numericality: {equal_to: 1}, unless: :base
|
||||||
validates :multiplier, numericality: {greater_than: 0, precision: true, scale: true}, if: :base
|
validates :multiplier, numericality: {greater_than: 0, precision: true, scale: true}, if: :base
|
||||||
|
|
||||||
@@ -22,56 +22,65 @@ class Unit < ApplicationRecord
|
|||||||
actionable_units = Arel::Table.new('actionable_units')
|
actionable_units = Arel::Table.new('actionable_units')
|
||||||
units = actionable_units.alias('units')
|
units = actionable_units.alias('units')
|
||||||
bases_units = arel_table.alias('bases_units')
|
bases_units = arel_table.alias('bases_units')
|
||||||
other_units = arel_table.alias('other_units')
|
# Use 'all_units' alias for correlated subqueries to reference the all_units CTE.
|
||||||
other_bases_units = arel_table.alias('other_bases_units')
|
# Cannot use arel_table.alias here because the CTE is named 'all_units', not 'units'.
|
||||||
|
other_units = Arel::Table.new('all_units').alias('other_units')
|
||||||
|
other_bases_units = Arel::Table.new('all_units').alias('other_bases_units')
|
||||||
sub_units = arel_table.alias('sub_units')
|
sub_units = arel_table.alias('sub_units')
|
||||||
|
|
||||||
Unit.with_recursive(actionable_units: [
|
Unit.with_recursive(
|
||||||
self.or(Unit.defaults).left_joins(:base)
|
# Renamed from 'units' to 'all_units' to avoid SQLite circular reference:
|
||||||
.where.not(
|
# SQLite treats any CTE in WITH RECURSIVE that references a table with the same
|
||||||
# Exclude Units that are/have default counterpart
|
# name as the CTE itself as a circular reference, even for non-recursive CTEs.
|
||||||
Arel::SelectManager.new.project(1).from(other_units)
|
all_units: self.or(Unit.defaults),
|
||||||
.outer_join(other_bases_units)
|
actionable_units: [
|
||||||
.on(other_units[:base_id].eq(other_bases_units[:id]))
|
# Read from all_units CTE (user+defaults) aliased as the units table name.
|
||||||
.where(
|
# Using AR::Relation (not Arel::SelectManager) to avoid extra parentheses
|
||||||
other_bases_units[:symbol].is_not_distinct_from(bases_units[:symbol])
|
# around the UNION part (visit_Arel_SelectManager always wraps in parens).
|
||||||
.and(other_units[:symbol].eq(arel_table[:symbol]))
|
Unit.from(Arel::Table.new('all_units').as(arel_table.name))
|
||||||
.and(other_units[:user_id].is_distinct_from(arel_table[:user_id]))
|
.joins("LEFT OUTER JOIN all_units bases_units ON bases_units.id = units.base_id")
|
||||||
).exists
|
.where.not(
|
||||||
)
|
# Exclude Units that are/have default counterpart
|
||||||
.select(
|
Arel::SelectManager.new.project(1).from(other_units)
|
||||||
arel_table[Arel.star],
|
.outer_join(other_bases_units)
|
||||||
# Decide if Unit can be im-/exported based on existing hierarchy:
|
.on(other_units[:base_id].eq(other_bases_units[:id]))
|
||||||
# * same base unit symbol has to exist
|
.where(
|
||||||
# * unit with subunits can only be ported to root
|
other_bases_units[:symbol].is_not_distinct_from(bases_units[:symbol])
|
||||||
arel_table[:base_id].eq(nil).or(
|
.and(other_units[:symbol].eq(arel_table[:symbol]))
|
||||||
(
|
.and(other_units[:user_id].is_distinct_from(arel_table[:user_id]))
|
||||||
Arel::SelectManager.new.project(1).from(other_units)
|
).exists
|
||||||
.join(sub_units).on(other_units[:id].eq(sub_units[:base_id]))
|
)
|
||||||
.where(
|
.select(
|
||||||
other_units[:symbol].eq(arel_table[:symbol])
|
arel_table[Arel.star],
|
||||||
.and(other_units[:user_id].is_distinct_from(arel_table[:user_id]))
|
# Decide if Unit can be im-/exported based on existing hierarchy:
|
||||||
).exists.not
|
# * same base unit symbol has to exist
|
||||||
).and(
|
# * unit with subunits can only be ported to root
|
||||||
Arel::SelectManager.new.project(1).from(other_bases_units)
|
arel_table[:base_id].eq(nil).or(
|
||||||
.where(
|
(
|
||||||
other_bases_units[:symbol].is_not_distinct_from(bases_units[:symbol])
|
Arel::SelectManager.new.project(1).from(other_units)
|
||||||
.and(other_bases_units[:user_id].is_distinct_from(bases_units[:user_id]))
|
.join(sub_units).on(other_units[:id].eq(sub_units[:base_id]))
|
||||||
).exists
|
.where(
|
||||||
)
|
other_units[:symbol].eq(arel_table[:symbol])
|
||||||
).as('portable')
|
.and(other_units[:user_id].is_distinct_from(arel_table[:user_id]))
|
||||||
),
|
).exists.not
|
||||||
# Fill base Units to display proper hierarchy. Duplicates will be removed
|
).and(
|
||||||
# by final group() - can't be deduplicated with UNION due to 'portable' field.
|
Arel::SelectManager.new.project(1).from(other_bases_units)
|
||||||
# Use ActiveRecord::Relation (not a raw SelectManager) so the SQLite Arel
|
.where(
|
||||||
# visitor does not wrap it in parentheses inside the UNION ALL CTE body.
|
other_bases_units[:symbol].is_not_distinct_from(bases_units[:symbol])
|
||||||
Unit.joins(
|
.and(other_bases_units[:user_id].is_distinct_from(bases_units[:user_id]))
|
||||||
arel_table.create_join(
|
).exists
|
||||||
actionable_units,
|
)
|
||||||
arel_table.create_on(actionable_units[:base_id].eq(arel_table[:id]))
|
).as('portable')
|
||||||
)
|
),
|
||||||
).select(arel_table[Arel.star], Arel::Nodes.build_quoted(nil).as('portable'))
|
# Fill base Units to display proper hierarchy. Duplicates will be removed
|
||||||
]).select(units: [:base_id, :symbol])
|
# by final group() - can't be deduplicated with UNION due to 'portable' field.
|
||||||
|
# Using AR::Relation instead of Arel::SelectManager to avoid extra parentheses
|
||||||
|
# around the UNION part (visit_Arel_SelectManager always wraps in parens).
|
||||||
|
Unit.from(Arel::Table.new('all_units').as(arel_table.name))
|
||||||
|
.joins("INNER JOIN actionable_units ON actionable_units.base_id = units.id")
|
||||||
|
.select(arel_table[Arel.star], Arel::Nodes.build_quoted(nil).as('portable'))
|
||||||
|
]
|
||||||
|
).select(units: [:base_id, :symbol])
|
||||||
.select(
|
.select(
|
||||||
units[:id].minimum.as('id'), # can be ANY_VALUE()
|
units[:id].minimum.as('id'), # can be ANY_VALUE()
|
||||||
units[:user_id].minimum.as('user_id'), # prefer non-default
|
units[:user_id].minimum.as('user_id'), # prefer non-default
|
||||||
@@ -87,7 +96,7 @@ class Unit < ApplicationRecord
|
|||||||
def self.ordering
|
def self.ordering
|
||||||
[arel_table.coalesce(Arel::Table.new(:bases_units)[:symbol], arel_table[:symbol]),
|
[arel_table.coalesce(Arel::Table.new(:bases_units)[:symbol], arel_table[:symbol]),
|
||||||
arel_table[:base_id].not_eq(nil),
|
arel_table[:base_id].not_eq(nil),
|
||||||
arel_table[:multiplier],
|
:multiplier,
|
||||||
arel_table[:symbol]]
|
arel_table[:symbol]]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,4 @@ Rails.application.configure do
|
|||||||
# config.action_view.annotate_rendered_view_with_filenames = true
|
# config.action_view.annotate_rendered_view_with_filenames = true
|
||||||
|
|
||||||
config.log_level = :info
|
config.log_level = :info
|
||||||
|
|
||||||
# Allow the default integration test host.
|
|
||||||
config.hosts << "www.example.com"
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
namespace :test do
|
|
||||||
desc "Run Rails tests against all supported database adapters (SQLite, MySQL)"
|
|
||||||
task :all_adapters do
|
|
||||||
# DATABASE_URL overrides the adapter from database.yml at runtime.
|
|
||||||
# MySQL requires the mysql2 gem: bundle install --with mysql
|
|
||||||
adapters = {
|
|
||||||
"SQLite" => {
|
|
||||||
"DATABASE_URL" => "sqlite3:db/test.sqlite3"
|
|
||||||
},
|
|
||||||
"MySQL" => {
|
|
||||||
"DATABASE_URL" => format(
|
|
||||||
"mysql2://%s:%s@%s/%s",
|
|
||||||
ENV.fetch("DATABASE_USERNAME", "root"),
|
|
||||||
ENV.fetch("DATABASE_PASSWORD", ""),
|
|
||||||
ENV.fetch("DATABASE_HOST", "127.0.0.1"),
|
|
||||||
ENV.fetch("DATABASE_NAME", "fixin_test")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
failed = []
|
|
||||||
|
|
||||||
adapters.each do |name, extra_env|
|
|
||||||
puts "\n#{"=" * 60}"
|
|
||||||
puts " Running tests with #{name}"
|
|
||||||
puts "=" * 60
|
|
||||||
|
|
||||||
env = ENV.to_h.merge("RAILS_ENV" => "test").merge(extra_env)
|
|
||||||
|
|
||||||
# Reset test database; db:drop may fail on first run — that's fine
|
|
||||||
system(env, "bin/rails db:drop")
|
|
||||||
unless system(env, "bin/rails db:create db:schema:load")
|
|
||||||
failed << "#{name} (database setup)"
|
|
||||||
next
|
|
||||||
end
|
|
||||||
|
|
||||||
failed << name unless system(env, "bin/rails test")
|
|
||||||
end
|
|
||||||
|
|
||||||
puts "\n#{"=" * 60}"
|
|
||||||
if failed.any?
|
|
||||||
puts " FAILED: #{failed.join(", ")}"
|
|
||||||
puts "=" * 60
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
puts " All adapters passed!"
|
|
||||||
puts "=" * 60
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,12 +1,8 @@
|
|||||||
require "test_helper"
|
require "test_helper"
|
||||||
|
|
||||||
class Default::UnitsControllerTest < ActionDispatch::IntegrationTest
|
class Default::UnitsControllerTest < ActionDispatch::IntegrationTest
|
||||||
setup do
|
|
||||||
sign_in users(:alice)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "should get index" do
|
test "should get index" do
|
||||||
get default_units_url
|
get units_defaults_index_url
|
||||||
assert_response :success
|
assert_response :success
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,9 +2,7 @@ require "test_helper"
|
|||||||
|
|
||||||
class UsersControllerTest < ActionDispatch::IntegrationTest
|
class UsersControllerTest < ActionDispatch::IntegrationTest
|
||||||
setup do
|
setup do
|
||||||
@admin = users(:admin)
|
@user = users(:one)
|
||||||
@user = users(:alice)
|
|
||||||
sign_in @admin
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "should get index" do
|
test "should get index" do
|
||||||
@@ -12,25 +10,39 @@ class UsersControllerTest < ActionDispatch::IntegrationTest
|
|||||||
assert_response :success
|
assert_response :success
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "should get new" do
|
||||||
|
get new_user_url
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
|
||||||
|
test "should create user" do
|
||||||
|
assert_difference("User.count") do
|
||||||
|
post users_url, params: { user: { email: @user.email, status: @user.status } }
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_redirected_to user_url(User.last)
|
||||||
|
end
|
||||||
|
|
||||||
test "should show user" do
|
test "should show user" do
|
||||||
get user_url(@user)
|
get user_url(@user)
|
||||||
assert_response :success
|
assert_response :success
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "should get edit" do
|
||||||
|
get edit_user_url(@user)
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
|
||||||
test "should update user" do
|
test "should update user" do
|
||||||
patch user_url(@user), params: { user: { status: :restricted } }, as: :turbo_stream
|
patch user_url(@user), params: { user: { email: @user.email, status: @user.status } }
|
||||||
assert_equal "restricted", @user.reload.status
|
assert_redirected_to user_url(@user)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "should not update self" do
|
test "should destroy user" do
|
||||||
patch user_url(@admin), params: { user: { status: :active } }, as: :turbo_stream,
|
assert_difference("User.count", -1) do
|
||||||
headers: { "HTTP_REFERER" => users_url }
|
delete user_url(@user)
|
||||||
assert_response :redirect
|
end
|
||||||
end
|
|
||||||
|
|
||||||
test "should forbid non-admin" do
|
assert_redirected_to users_url
|
||||||
sign_in @user
|
|
||||||
get users_url
|
|
||||||
assert_response :forbidden
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,10 +2,6 @@ ENV["RAILS_ENV"] ||= "test"
|
|||||||
require_relative "../config/environment"
|
require_relative "../config/environment"
|
||||||
require "rails/test_help"
|
require "rails/test_help"
|
||||||
|
|
||||||
class ActionDispatch::IntegrationTest
|
|
||||||
include Devise::Test::IntegrationHelpers
|
|
||||||
end
|
|
||||||
|
|
||||||
class ActiveSupport::TestCase
|
class ActiveSupport::TestCase
|
||||||
# Run tests in parallel with specified workers
|
# Run tests in parallel with specified workers
|
||||||
parallelize(workers: :number_of_processors)
|
parallelize(workers: :number_of_processors)
|
||||||
|
|||||||
Reference in New Issue
Block a user