diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..7dc2a5a
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,50 @@
+name: "CI"
+on:
+ push:
+ branches: [ "main" ]
+ pull_request:
+ branches: [ "main" ]
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ services:
+ postgres:
+ image: postgres:12-alpine
+ ports:
+ - "5432:5432"
+ env:
+ POSTGRES_DB: rails_test
+ POSTGRES_USER: rails
+ POSTGRES_PASSWORD: password
+ env:
+ RAILS_ENV: test
+ DATABASE_URL: "postgres://rails:password@localhost:5432/rails_test"
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v3
+ # Add or replace dependency steps here
+ - name: Install Ruby and gems
+ uses: ruby/setup-ruby@v1
+ with:
+ bundler-cache: true
+ # Add or replace database setup steps here
+ - name: Set up database schema
+ run: bin/rails db:schema:load
+ # Add or replace test runners here
+ - name: Run specs
+ run: bin/rspec
+
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v3
+ - name: Install Ruby and gems
+ uses: ruby/setup-ruby@v1
+ with:
+ bundler-cache: true
+ # Add or replace any other lints here
+ - name: Security audit application code
+ run: bin/brakeman
+ - name: Lint Ruby files
+ run: bin/rubocop --parallel
diff --git a/.gitignore b/.gitignore
index 7061489..f84af9b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,5 @@
# Ignore master key for decrypting credentials and more.
/config/master.key
+
+.byebug_history
diff --git a/.rubocop.yml b/.rubocop.yml
index 984146f..732deec 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -1,5 +1,6 @@
require:
- rubocop-capybara
+ - rubocop-factory_bot
- rubocop-performance
- rubocop-rspec
- rubocop-rails
@@ -14,5 +15,8 @@ AllCops:
- lib/tasks/*
NewCops: enable
+Rails/I18nLocaleTexts:
+ Enabled: false
+
Style/Documentation:
Enabled: false
\ No newline at end of file
diff --git a/.ruby-version b/.ruby-version
deleted file mode 100644
index 72b3400..0000000
--- a/.ruby-version
+++ /dev/null
@@ -1 +0,0 @@
-ruby-3.2.1
diff --git a/Gemfile b/Gemfile
index d108fa6..4feeba3 100644
--- a/Gemfile
+++ b/Gemfile
@@ -18,12 +18,6 @@ gem 'tzinfo-data', platforms: %i[windows jruby]
group :development, :test do
gem 'brakeman'
gem 'byebug', platforms: %i[mri mingw x64_mingw]
- gem 'rspec-rails'
- gem 'rubocop', require: false
- gem 'rubocop-capybara', require: false
- gem 'rubocop-performance', require: false
- gem 'rubocop-rails', require: false
- gem 'rubocop-rspec', require: false
end
group :development do
@@ -32,5 +26,13 @@ end
group :test do
gem 'capybara'
- gem 'selenium-webdriver'
+ gem 'factory_bot_rails'
+ gem 'rspec-rails'
+ gem 'rubocop', require: false
+ gem 'rubocop-capybara', require: false
+ gem 'rubocop-factory_bot', require: false
+ gem 'rubocop-performance', require: false
+ gem 'rubocop-rails', require: false
+ gem 'rubocop-rspec', require: false
+ gem 'webdrivers'
end
diff --git a/Gemfile.lock b/Gemfile.lock
index 7e4570d..e60a901 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -131,6 +131,11 @@ GEM
drb (2.2.0)
ruby2_keywords
erubi (1.12.0)
+ factory_bot (6.4.4)
+ activesupport (>= 5.0.0)
+ factory_bot_rails (6.4.2)
+ factory_bot (~> 6.4)
+ railties (>= 5.0.0)
globalid (1.2.1)
activesupport (>= 6.1)
i18n (1.14.1)
@@ -262,7 +267,7 @@ GEM
ruby-progressbar (1.13.0)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
- selenium-webdriver (4.16.0)
+ selenium-webdriver (4.10.0)
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0)
websocket (~> 1.0)
@@ -290,6 +295,10 @@ GEM
activemodel (>= 6.0.0)
bindex (>= 0.4.0)
railties (>= 6.0.0)
+ webdrivers (5.3.1)
+ nokogiri (~> 1.6)
+ rubyzip (>= 1.3.0)
+ selenium-webdriver (~> 4.0, < 4.11)
webrick (1.8.1)
websocket (1.2.10)
websocket-driver (0.7.6)
@@ -309,6 +318,7 @@ DEPENDENCIES
brakeman
byebug
capybara
+ factory_bot_rails
importmap-rails
jbuilder
pg (~> 1.1)
@@ -317,15 +327,16 @@ DEPENDENCIES
rspec-rails
rubocop
rubocop-capybara
+ rubocop-factory_bot
rubocop-performance
rubocop-rails
rubocop-rspec
- selenium-webdriver
sprockets-rails
stimulus-rails
turbo-rails
tzinfo-data
web-console
+ webdrivers
RUBY VERSION
ruby 3.2.2p53
diff --git a/README.md b/README.md
index 3fd16a6..fe69ce6 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,8 @@
# TransactionCategorization Rails App
A simple [Rails](https://rubyonrails.org/) application that categorizes banking transaction via Machine Learning (AI).
+![Build Status](https://github.com/agilous/transcat/actions/workflows/ci.yml/badge.svg)
+
## Dependencies
* [asdf](https://asdf-vm.com/#/)
* [Ruby](https://www.ruby-lang.org/en/) 3.2.2
diff --git a/app/assets/images/.keep b/app/assets/images/.keep
new file mode 100644
index 0000000..e69de29
diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb
new file mode 100644
index 0000000..b5d6f0e
--- /dev/null
+++ b/app/controllers/categories_controller.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+class CategoriesController < ApplicationController
+ before_action :set_category, only: %i[show edit update destroy]
+
+ # GET /categories or /categories.json
+ def index
+ @categories = Category.all
+ end
+
+ # GET /categories/1 or /categories/1.json
+ def show; end
+
+ # GET /categories/new
+ def new
+ @category = Category.new
+ end
+
+ # GET /categories/1/edit
+ def edit; end
+
+ # POST /categories or /categories.json
+ def create
+ @category = Category.new(category_params)
+
+ respond_to do |format|
+ if @category.save
+ format.html { redirect_to category_url(@category), notice: 'Category was successfully created.' }
+ format.json { render :show, status: :created, location: @category }
+ else
+ format.html { render :new, status: :unprocessable_entity }
+ format.json { render json: @category.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ # PATCH/PUT /categories/1 or /categories/1.json
+ def update
+ respond_to do |format|
+ if @category.update(category_params)
+ format.html { redirect_to category_url(@category), notice: 'Category was successfully updated.' }
+ format.json { render :show, status: :ok, location: @category }
+ else
+ format.html { render :edit, status: :unprocessable_entity }
+ format.json { render json: @category.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ # DELETE /categories/1 or /categories/1.json
+ def destroy
+ @category.destroy!
+
+ respond_to do |format|
+ format.html { redirect_to categories_url, notice: 'Category was successfully destroyed.' }
+ format.json { head :no_content }
+ end
+ end
+
+ private
+
+ # Use callbacks to share common setup or constraints between actions.
+ def set_category
+ @category = Category.find(params[:id])
+ end
+
+ # Only allow a list of trusted parameters through.
+ def category_params
+ params.require(:category).permit(:name)
+ end
+end
diff --git a/app/helpers/categories_helper.rb b/app/helpers/categories_helper.rb
new file mode 100644
index 0000000..46512be
--- /dev/null
+++ b/app/helpers/categories_helper.rb
@@ -0,0 +1,4 @@
+# frozen_string_literal: true
+
+module CategoriesHelper
+end
diff --git a/app/models/category.rb b/app/models/category.rb
new file mode 100644
index 0000000..edb6971
--- /dev/null
+++ b/app/models/category.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class Category < ApplicationRecord
+ validates :name, uniqueness: true, presence: true
+end
diff --git a/app/views/categories/_category.html.erb b/app/views/categories/_category.html.erb
new file mode 100644
index 0000000..8769e08
--- /dev/null
+++ b/app/views/categories/_category.html.erb
@@ -0,0 +1,7 @@
+
+
+ Name:
+ <%= category.name %>
+
+
+
diff --git a/app/views/categories/_category.json.jbuilder b/app/views/categories/_category.json.jbuilder
new file mode 100644
index 0000000..b7dac0e
--- /dev/null
+++ b/app/views/categories/_category.json.jbuilder
@@ -0,0 +1,4 @@
+# frozen_string_literal: true
+
+json.extract! category, :id, :name, :created_at, :updated_at
+json.url category_url(category, format: :json)
diff --git a/app/views/categories/_form.html.erb b/app/views/categories/_form.html.erb
new file mode 100644
index 0000000..74632c0
--- /dev/null
+++ b/app/views/categories/_form.html.erb
@@ -0,0 +1,22 @@
+<%= form_with(model: category) do |form| %>
+ <% if category.errors.any? %>
+
+
<%= pluralize(category.errors.count, "error") %> prohibited this category from being saved:
+
+
+ <% category.errors.each do |error| %>
+ - <%= error.full_message %>
+ <% end %>
+
+
+ <% end %>
+
+
+ <%= form.label :name, style: "display: block" %>
+ <%= form.text_field :name %>
+
+
+
+ <%= form.submit %>
+
+<% end %>
diff --git a/app/views/categories/edit.html.erb b/app/views/categories/edit.html.erb
new file mode 100644
index 0000000..996ba21
--- /dev/null
+++ b/app/views/categories/edit.html.erb
@@ -0,0 +1,10 @@
+Editing category
+
+<%= render "form", category: @category %>
+
+
+
+
+ <%= link_to "Show this category", @category %> |
+ <%= link_to "Back to categories", categories_path %>
+
diff --git a/app/views/categories/index.html.erb b/app/views/categories/index.html.erb
new file mode 100644
index 0000000..58d8158
--- /dev/null
+++ b/app/views/categories/index.html.erb
@@ -0,0 +1,14 @@
+<%= notice %>
+
+Categories
+
+
+ <% @categories.each do |category| %>
+ <%= render category %>
+
+ <%= link_to "Show this category", category %>
+
+ <% end %>
+
+
+<%= link_to "New category", new_category_path %>
diff --git a/app/views/categories/index.json.jbuilder b/app/views/categories/index.json.jbuilder
new file mode 100644
index 0000000..0fd4887
--- /dev/null
+++ b/app/views/categories/index.json.jbuilder
@@ -0,0 +1,3 @@
+# frozen_string_literal: true
+
+json.array! @categories, partial: 'categories/category', as: :category
diff --git a/app/views/categories/new.html.erb b/app/views/categories/new.html.erb
new file mode 100644
index 0000000..8df9c54
--- /dev/null
+++ b/app/views/categories/new.html.erb
@@ -0,0 +1,9 @@
+New category
+
+<%= render "form", category: @category %>
+
+
+
+
+ <%= link_to "Back to categories", categories_path %>
+
diff --git a/app/views/categories/show.html.erb b/app/views/categories/show.html.erb
new file mode 100644
index 0000000..dd28ade
--- /dev/null
+++ b/app/views/categories/show.html.erb
@@ -0,0 +1,10 @@
+<%= notice %>
+
+<%= render @category %>
+
+
+ <%= link_to "Edit this category", edit_category_path(@category) %> |
+ <%= link_to "Back to categories", categories_path %>
+
+ <%= button_to "Destroy this category", @category, method: :delete %>
+
diff --git a/app/views/categories/show.json.jbuilder b/app/views/categories/show.json.jbuilder
new file mode 100644
index 0000000..00fa333
--- /dev/null
+++ b/app/views/categories/show.json.jbuilder
@@ -0,0 +1,3 @@
+# frozen_string_literal: true
+
+json.partial! 'categories/category', category: @category
diff --git a/bin/brakeman b/bin/brakeman
new file mode 100755
index 0000000..b4fe8de
--- /dev/null
+++ b/bin/brakeman
@@ -0,0 +1,27 @@
+#!/usr/bin/env ruby
+# frozen_string_literal: true
+
+#
+# This file was generated by Bundler.
+#
+# The application 'brakeman' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
+
+bundle_binstub = File.expand_path("bundle", __dir__)
+
+if File.file?(bundle_binstub)
+ if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
+ load(bundle_binstub)
+ else
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
+Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
+ end
+end
+
+require "rubygems"
+require "bundler/setup"
+
+load Gem.bin_path("brakeman", "brakeman")
diff --git a/bin/ci b/bin/ci
index 21f8824..6dd454c 100755
--- a/bin/ci
+++ b/bin/ci
@@ -18,10 +18,15 @@ Dir.chdir(APP_ROOT) do
ruby_linting_passed = system("bin/rubocop")
terminal_message("Done.\n\n")
+ terminal_message("Running Brakeman Scan...")
+ brakeman_passed = system("bin/brakeman")
+ terminal_message("Done.\n\n")
+
pass_fail(ruby_tests_passed, "Ruby Tests")
pass_fail(ruby_linting_passed, "Ruby Linter")
+ pass_fail(brakeman_passed, "Brakeman Scan")
- return 1 unless ruby_tests_passed && ruby_linting_passed
+ return 1 unless ruby_tests_passed && ruby_linting_passed && brakeman_passed
return 0
end
diff --git a/config/brakeman.yml b/config/brakeman.yml
new file mode 100644
index 0000000..5b45b5d
--- /dev/null
+++ b/config/brakeman.yml
@@ -0,0 +1,8 @@
+---
+:rails3: false
+:rails4: false
+:rails5: false
+:rails6: false
+:rails7: true
+:quiet: true
+:summary_only: :no_summary
\ No newline at end of file
diff --git a/config/routes.rb b/config/routes.rb
index a125ef0..fbd66dd 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,4 +1,5 @@
Rails.application.routes.draw do
+ resources :categories
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
# Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
@@ -6,5 +7,5 @@
get "up" => "rails/health#show", as: :rails_health_check
# Defines the root path route ("/")
- # root "posts#index"
+ root "categories#index"
end
diff --git a/db/migrate/20231229203335_create_categories.rb b/db/migrate/20231229203335_create_categories.rb
new file mode 100644
index 0000000..da6ce09
--- /dev/null
+++ b/db/migrate/20231229203335_create_categories.rb
@@ -0,0 +1,11 @@
+class CreateCategories < ActiveRecord::Migration[7.2]
+ def change
+ create_table :categories do |t|
+ t.string :name, null: false
+
+ t.timestamps
+ end
+
+ add_index :categories, [:name], unique: true
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 83701a2..7875a37 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,8 +10,15 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.2].define(version: 0) do
+ActiveRecord::Schema[7.2].define(version: 2023_12_29_203335) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
+ create_table "categories", force: :cascade do |t|
+ t.string "name", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["name"], name: "index_categories_on_name", unique: true
+ end
+
end
diff --git a/spec/factories/category_factory.rb b/spec/factories/category_factory.rb
new file mode 100644
index 0000000..c4294f9
--- /dev/null
+++ b/spec/factories/category_factory.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :category do
+ name { ['Clothing', 'Dining Out', 'Groceries', 'Rent', 'Transportation'].sample }
+ end
+end
diff --git a/spec/features/category_spec.rb b/spec/features/category_spec.rb
new file mode 100644
index 0000000..dfd9222
--- /dev/null
+++ b/spec/features/category_spec.rb
@@ -0,0 +1,121 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe 'Category' do
+ describe 'creating' do
+ before do
+ visit new_category_path
+
+ fill_in 'Name', with: 'Gas'
+ click_button 'Create Category'
+ end
+
+ it 'displays a success message' do
+ expect(page).to have_text 'Category was successfully created.'
+ end
+
+ it 'displays the new Category' do
+ expect(page).to have_text 'Name: Gas'
+ end
+
+ context 'when attempting to create a duplicate category' do
+ before do
+ visit new_category_path
+
+ fill_in 'Name', with: Category.last.name
+ click_button 'Create Category'
+ end
+
+ it 'displays an error message' do
+ expect(page).to have_text 'Name has already been taken'
+ end
+
+ it 'returns to the new view' do
+ expect(page).to have_current_path(new_category_path)
+ end
+ end
+ end
+
+ describe 'deleting' do
+ let(:category) { create(:category) }
+
+ before do
+ visit category_path(category)
+
+ click_button 'Destroy this category'
+ end
+
+ it 'displays a success message' do
+ expect(page).to have_text 'Category was successfully destroyed.'
+ end
+
+ it 'returns to the index view' do
+ expect(page).to have_current_path(categories_path)
+ end
+ end
+
+ describe 'indexing' do
+ let(:category_names) { %w[Food Gas Utilities Rent] }
+ let!(:categories) { category_names.map { |name| create(:category, name:) } }
+
+ before do
+ visit categories_path
+ end
+
+ it 'lists all the category names' do
+ expect(categories.pluck(&:name)).to(be_all { |name| !page.text(/Name: #{name}/).nil? })
+ end
+ end
+
+ describe 'showing' do
+ let(:category) { create(:category) }
+
+ before do
+ visit category_path(category)
+ end
+
+ it 'displays the category name' do
+ expect(page).to have_text "Name: #{category.name}"
+ end
+ end
+
+ describe 'updating' do
+ let(:category) { create(:category) }
+ let(:new_name) { "#{category.name} and more!" }
+
+ before do
+ visit edit_category_path(category)
+
+ fill_in 'Name', with: new_name
+ click_button 'Update Category'
+ end
+
+ it 'displays a success message' do
+ expect(page).to have_text 'Category was successfully updated.'
+ end
+
+ it 'displays the updated Category' do
+ expect(page).to have_text "Name: #{new_name}"
+ end
+
+ context 'when attempting to update the name to an existing category name' do
+ let!(:another_category) { create(:category, name: "#{category.name} and still more!") }
+
+ before do
+ visit edit_category_path(category)
+
+ fill_in 'Name', with: another_category.name
+ click_button 'Update Category'
+ end
+
+ it 'displays an error message' do
+ expect(page).to have_text 'Name has already been taken'
+ end
+
+ it 'returns to the new view' do
+ expect(page).to have_current_path(edit_category_path(category))
+ end
+ end
+ end
+end
diff --git a/spec/models/category_spec.rb b/spec/models/category_spec.rb
new file mode 100644
index 0000000..e627c73
--- /dev/null
+++ b/spec/models/category_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Category do
+ subject(:category) { create(:category) }
+
+ it { is_expected.to be_valid }
+
+ context 'when name is nil' do
+ it 'raises an exception' do
+ expect { create(:category, name: nil) }.to raise_exception(ActiveRecord::RecordInvalid)
+ end
+ end
+end
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index 254658c..99ebdaf 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -22,7 +22,7 @@
# directory. Alternatively, in the individual `*_spec.rb` files, manually
# require only the support files necessary.
#
-# Rails.root.glob('spec/support/**/*.rb').sort.each { |f| require f }
+Rails.root.glob('spec/support/**/*.rb').sort.each { |f| require f }
# Checks for pending migrations and applies them before tests are run.
# If you are not using ActiveRecord, you can remove these lines.
diff --git a/spec/requests/categories_spec.rb b/spec/requests/categories_spec.rb
new file mode 100644
index 0000000..ac375da
--- /dev/null
+++ b/spec/requests/categories_spec.rb
@@ -0,0 +1,113 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe '/categories' do
+ describe 'GET /index' do
+ it 'renders a successful response' do
+ get categories_url
+ expect(response).to be_successful
+ end
+ end
+
+ describe 'GET /show' do
+ let!(:category) { create(:category) }
+
+ it 'renders a successful response' do
+ get category_url(category)
+ expect(response).to be_successful
+ end
+ end
+
+ describe 'GET /new' do
+ it 'renders a successful response' do
+ get new_category_url
+ expect(response).to be_successful
+ end
+ end
+
+ describe 'GET /edit' do
+ let!(:category) { create(:category) }
+
+ it 'renders a successful response' do
+ get edit_category_url(category)
+ expect(response).to be_successful
+ end
+ end
+
+ describe 'POST /create' do
+ context 'with valid parameters' do
+ let(:valid_attributes) { { name: 'Food' } }
+
+ it 'creates a new Category' do
+ expect do
+ post categories_url, params: { category: valid_attributes }
+ end.to change(Category, :count).by(1)
+ end
+
+ it 'redirects to the created category' do
+ post categories_url, params: { category: valid_attributes }
+ expect(response).to redirect_to(category_url(Category.last))
+ end
+ end
+
+ context 'with invalid parameters' do
+ let(:invalid_attributes) { { name: nil } }
+
+ it 'does not create a new Category' do
+ expect do
+ post categories_url, params: { category: invalid_attributes }
+ end.not_to change(Category, :count)
+ end
+
+ it "renders a response with 422 status (i.e. to display the 'new' template)" do
+ post categories_url, params: { category: invalid_attributes }
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+ end
+
+ describe 'PATCH /update' do
+ context 'with valid parameters' do
+ let!(:category) { create(:category) }
+ let(:new_attributes) { { name: "#{category.name} and more!" } }
+
+ it 'updates the requested category' do
+ patch category_url(category), params: { category: new_attributes }
+ category.reload
+ expect(category.reload.name).to eq(new_attributes[:name])
+ end
+
+ it 'redirects to the category' do
+ patch category_url(category), params: { category: new_attributes }
+ category.reload
+ expect(response).to redirect_to(category_url(category))
+ end
+ end
+
+ context 'with invalid parameters' do
+ let!(:category) { create(:category) }
+ let(:invalid_attributes) { { name: nil } }
+
+ it "renders a response with 422 status (i.e. to display the 'edit' template)" do
+ patch category_url(category), params: { category: invalid_attributes }
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+ end
+
+ describe 'DELETE /destroy' do
+ let!(:category) { create(:category) }
+
+ it 'destroys the requested category' do
+ expect do
+ delete category_url(category)
+ end.to change(Category, :count).by(-1)
+ end
+
+ it 'redirects to the categories list' do
+ delete category_url(category)
+ expect(response).to redirect_to(categories_url)
+ end
+ end
+end
diff --git a/spec/routing/categories_routing_spec.rb b/spec/routing/categories_routing_spec.rb
new file mode 100644
index 0000000..175e2ed
--- /dev/null
+++ b/spec/routing/categories_routing_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe CategoriesController do
+ describe 'routing' do
+ it 'routes to #index' do
+ expect(get: '/categories').to route_to('categories#index')
+ end
+
+ it 'routes to #new' do
+ expect(get: '/categories/new').to route_to('categories#new')
+ end
+
+ it 'routes to #show' do
+ expect(get: '/categories/1').to route_to('categories#show', id: '1')
+ end
+
+ it 'routes to #edit' do
+ expect(get: '/categories/1/edit').to route_to('categories#edit', id: '1')
+ end
+
+ it 'routes to #create' do
+ expect(post: '/categories').to route_to('categories#create')
+ end
+
+ it 'routes to #update via PUT' do
+ expect(put: '/categories/1').to route_to('categories#update', id: '1')
+ end
+
+ it 'routes to #update via PATCH' do
+ expect(patch: '/categories/1').to route_to('categories#update', id: '1')
+ end
+
+ it 'routes to #destroy' do
+ expect(delete: '/categories/1').to route_to('categories#destroy', id: '1')
+ end
+ end
+end
diff --git a/spec/routing/root_routing_spec.rb b/spec/routing/root_routing_spec.rb
new file mode 100644
index 0000000..12ec444
--- /dev/null
+++ b/spec/routing/root_routing_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe 'root routing' do
+ it 'routes to categories#index' do
+ expect(get: '/').to route_to('categories#index')
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 409c64b..2773164 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -91,4 +91,15 @@
# # test failures related to randomization by passing the same `--seed` value
# # as the one that triggered the failure.
# Kernel.srand config.seed
+
+ config.before(:each, type: :feature) do |example|
+ # Available drivers :rack_test, :selenium, :selenium_headless, :selenium_chrome, :selenium_chrome_headless
+ javascript_driver = example.metadata[:js_driver] || ENV.fetch('JAVASCRIPT_DRIVER', :selenium_headless).to_sym
+ Capybara.current_driver = javascript_driver
+ Capybara.server = :puma, { Silent: true }
+ end
+
+ config.after(:each, type: :feature) do
+ Capybara.use_default_driver
+ end
end
diff --git a/spec/support/factory_bot.rb b/spec/support/factory_bot.rb
new file mode 100644
index 0000000..2e7665c
--- /dev/null
+++ b/spec/support/factory_bot.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+RSpec.configure do |config|
+ config.include FactoryBot::Syntax::Methods
+end